I'm new to MVC5/C# (fresh off a Silverlight project) and have a web application (not ASP.net) that I'm working on. I can't figure out how to get the value from a dropdown list that is populated from a ViewBag and not the model. Everything I've seen is geared towards ASP.NET and/or populating the dropdown from the model.
I have this model for shifts:
public class Shift
{
public Guid ShiftID { get; set; }
public string AreaOfOperation { get; set; }
public string UserName { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
}
And this for AreaOfOperations:
public class AreaOfOperations
{
public Guid AreaOfOperationsID { get; set; }
public String AreaOfOperation { get; set; }
public bool InUse { get; set; }
}
The relevant controller code, which populates the view nicely with a working dropdown:
public ActionResult Create(DateTime? datetime)
{
List<AreaOfOperations> list = db.AreaOfOperations.Where(i => i.InUse == true).OrderBy(aoo => aoo.AreaOfOperation).ToList();
ViewBag.DropDownAOOs = new SelectList(list, "AreaOfOperationsID", "AreaOfOperation");
Shift shift = new Shift();
shift.ShiftID = Guid.NewGuid();
shift.StartTime = DateTime.Now;
shift.UserName = User.Identity.Name;
return View(shift);
}
// POST: Shifts/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ShiftID,AreaOfOperations,UserName,StartTime")] Shift shift)
{
try
{
if (ModelState.IsValid)
{
shift.ShiftID = Guid.NewGuid();
db.Shifts.Add(shift);
db.SaveChanges();
return RedirectToAction("Index");
}
}
catch (DataException /* dex */)
{
//Log the error (uncomment dex variable name and add a line here to write a log.
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
}
return View(shift);
}
And my view:
#model CRMgr5.Models.Shift
#{
ViewBag.Title = "Start Shift";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Shift</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.AreaOfOperations, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("AreaOfOperation", ViewBag.DropDownAOOs as SelectList, new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.UserName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.UserName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.UserName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.StartTime, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.StartTime, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.StartTime, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input id="btnStartShift" type="submit" value="Start Shift" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Any help would be greatly appreciated. Thanks.
In the drop down list you named your select as "AreaOfOperation" but the model property is called "AreaOfOperations." Hence the binder will not be able to bind it.
As someone here already suggested you should use strongly typed html helpers such as DropDownListFor:
#Html.DropDownListFor(m => m.AreaOfOperations, ViewBag.DropDownAOOs as SelectList)
You did it for the label not sure why you opted not to use it when generating a drop down list?
I just recreated the whole thing and it worked fine
I removed the s of AreaOfOperations in your Bind Attribute
[Bind(Include = "ShiftID,AreaOfOperation(s),UserName,StartTime")]
As far as i know, you can remove this parameter attribute alltogether.
This is only used when you only want to bind to certain Attributes of your view model.
However there was one mistake: you have to repopulate the Select List if your ModelState is not valid. Otherwise your
return View(shift);
does not have the data to render a new SelectList.
Another approach is that you put the data in your ViewModel and initialize it in the default constructor. Then you dont have to worry about the data or casting.
Related
I have created a model with a controller and a view in my ASP.NET MVC application. Initially, when a new application is created using the create action, the user doesn't have the possibility to fill in 5 of the parts of the model (see code).
Editing is only possible when logging in as an admin, not as any user. Getting the edit page for the right application connected to the right user is not a problem. HOWEVER, when I fill in the checkboxes and write any comments etc. in the 'edit mode', and press submit, nothing happens. It seems the changes are not registered by the program (db.SaveChanges() doesn't work??)
Please, do someone know how I can fix this, or to begin with, what the problem is? It is almost as if the save button is just a shell, so might there me a connection or something missing?
Thank you for your time.
code:
part of the model (last 5 are not filled in in 'create mode'
[Display(Name = "Course for master")]
public string Course_Master { get; set; }
[Required]
[Display(Name = "Words for Office")]
[MaxLength(3000)]
public string Motivation { get; set; }
//[DataType(DataType.Upload)]
[Display(Name = "Upload Resume")]
//[Required(ErrorMessage = "Please choose file to upload")]
public string Resume { get; set; }
//these 5 are the ones I want to edit
public bool Interview { get; set; } //checkbox
public string Comments { get; set; }
public string Notes { get; set; }
public bool Unfit { get; set; } //checkbox
public bool Candidate { get; set; } //checkbox
razor page
#model NEA.Models.Application
#{
ViewBag.Title = "Edit";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Edit</h2>
#using (Html.BeginForm("Edit","Applications", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Application</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.UserId)
<div class="form-group">
#Html.LabelFor(model => model.Interview, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<div class="checkbox">
#Html.EditorFor(model => model.Interview)
#Html.ValidationMessageFor(model => model.Interview, "", new { #class = "text-danger" })
</div>
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Comments, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Comments, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Comments, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Notes, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Notes, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Notes, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Unfit, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<div class="checkbox">
#Html.EditorFor(model => model.Unfit)
#Html.ValidationMessageFor(model => model.Unfit, "", new { #class = "text-danger" })
</div>
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Candidate, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<div class="checkbox">
#Html.EditorFor(model => model.Candidate)
#Html.ValidationMessageFor(model => model.Candidate, "", new { #class = "text-danger" })
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
action
// GET: Applications/Edit/5
public ActionResult Edit(string id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Application application = db.Applications.Find(id);
if (application == null)
{
return HttpNotFound();
}
return View(application);
}
// POST: Applications/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Application application)
{
if (ModelState.IsValid)
{
db.Entry(application).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(application);
}
Edit
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Application application)
{
//db.Entry(application).State = EntityState.Modified;
db.Entry(application).Property(o => o.Interview).IsModified = true;
db.Entry(application).Property(o => o.Comments).IsModified = true;
db.Entry(application).Property(o => o.Notes).IsModified = true;
db.Entry(application).Property(o => o.Unfit).IsModified = true;
db.Entry(application).Property(o => o.Candidate).IsModified = true;
db.SaveChanges();
return RedirectToAction("Index");
//return View(application);
}
If you want to change certain values, according to your above code does not know which property is modified. You can set properties as isModified=true that you want to change value of properties.
EDIT:
db.Application.Attach(application);
db.Entry(application).Property(o => o.Interview).IsModified = true;
db.Entry(application).Property(o => o.Comments).IsModified = true;
db.SaveChanges();
return RedirectToAction("Index");
Or you can make this below way with list;
var included= new[] { "Interview ", "Comments " };
var entry = context.Entry(obj);
entry.State = EntityState.Modified;
foreach (var name in included)
{
entry.Property(name).IsModified = true;
}
I figured out how to fix it, whilst keeping #Html.BeginForm() empty
edit action
// POST: Applications/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Application application, string id)
{
var data = db.Applications.Find(id);
data.Interview = application.Interview;
data.Comments = application.Comments;
data.Notes = application.Notes;
data.Unfit = application.Unfit;
data.Candidate = application.Candidate;
db.SaveChanges();
return RedirectToAction("Index");
}
I am having an issue where I think I have set things up correctly, however the results are alway nil.
Here's the three models:
public class FamilyMember
{
[Key]
public int id { get; set; }
[Required]
[Display(Name = "First Name")]
public string firstName { get; set; }
[Required]
[Display(Name = "Surname")]
public string surname { get; set; }
[Required]
[Display(Name = "Date of Birth")]
public DateTime dob { get; set; }
public virtual FamilyRelationship FamilyRelationship { get; set; }
[Display(Name = "Full Name")]
public string fullName
{
get
{
return string.Format("{0} {1}", firstName, surname);
}
}
}
public class RelationshipType
{
[Key]
public int id { get; set; }
[Required]
[Display(Name="Relationship Type")]
public string relationshipType { get; set; }
public virtual FamilyRelationship FamilyRelationship { get; set; }
}
public class FamilyRelationship
{
[Key]
public int id { get; set; }
[ForeignKey("FamilyMembers")]
[Display(Name = "First Family Member")]
public int familyMemberPrimary { get; set; }
[ForeignKey("FamilyMembers")]
[Display(Name = "Second Family Member")]
public int familyMemberSecondary { get; set; }
[Display(Name = "Relationship Type")]
public int relationshipType { get; set; }
public virtual ICollection<FamilyMember> FamilyMembers { get; set; }
public virtual ICollection<RelationshipType> RelationshipTypes { get; set; }
}
So, I have successfully added data to FamilyMember and RelationshipType and the CRUD is working perfectly.
The problem is found in the Create Controller/View of FamilyRelationship. The dropdown works perfectly and shows the family members in the two associated menus and the relationship also shows on the relationType dropdown. However, when I click create all values are set to null.
Create Controller:
// GET: FamilyRelationships/Create
public ActionResult Create()
{
ViewBag.familyMember = new SelectList(db.FamilyMembers, "id", "fullName");
ViewBag.relationship = new SelectList(db.RelationshipTypes, "id", "relationshipType");
return View();
}
// POST: FamilyRelationships/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "id,familyMemberPrimary,familyMemberSecondary,relationshipType")] FamilyRelationship familyRelationship)
{
if (ModelState.IsValid)
{
db.FamilyRelationships.Add(familyRelationship);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(familyRelationship);
}
Create View:
#model FamilyTree.Models.FamilyRelationship
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>FamilyRelationship</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.familyMemberPrimary, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#*#Html.EditorFor(model => model.familyMemberPrimary, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.familyMemberPrimary, "", new { #class = "text-danger" })*#
#Html.DropDownList("familyMember", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.familyMemberPrimary, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.familyMemberSecondary, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("familyMember", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.familyMemberPrimary, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.relationshipType, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("relationship", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.relationshipType, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Please let me know where am I going wrong and if possible provide an example to make this work.
#Html.DropDownList("familyMember"
you need to use the actual property names most likely
#Html.DropDownList("familyMemberPrimary"
you'd also have to rename the viewbag property to match for the items to show up.. or use dropdownlistfor
#Html.DropDownListFor(a => a.familyMemberPrimary, (SelectList)ViewBag.familyMember , new { #class = "form-control" })
you also need to add a dropdownlist for familyMemberSecondary
#Html.DropDownListFor(a => a.familyMemberSecondary, (SelectList)ViewBag.familyMember , new { #class = "form-control" })
This should get you pretty close..
#model FamilyTree.Models.FamilyRelationship
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>FamilyRelationship</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.familyMemberPrimary, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(a => a.familyMemberPrimary, (SelectList)ViewBag.familyMember, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.familyMemberPrimary, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.familyMemberSecondary, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(a => a.familyMemberSecondary, (SelectList)ViewBag.familyMember, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.familyMemberSecondary, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.relationshipType, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(a => a.relationshipType, (SelectList)ViewBag.relationship, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.relationshipType, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
make sure you re set your ViewBag properties after a failed POST
DotNetFiddle Example
This is the expected behaviour. Remember Http is stateless. So you need to reload your dropdown data before returning to the view
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "id,familyMemberPrimary,familyMemberSecondary,
relationshipType")] FamilyRelationship familyRelationship)
{
if (ModelState.IsValid)
{
db.FamilyRelationships.Add(familyRelationship);
db.SaveChanges();
return RedirectToAction("Index");
}
//Let's reload the data for dropdown.
ViewBag.familyMember = new SelectList(db.FamilyMembers, "id", "fullName");
ViewBag.relationship = new SelectList(db.RelationshipTypes, "id", "relationshipType");
return View(familyRelationship);
}
EDIT: As per comment
The values within the FamilyRelationship so familyMemberPrimary,
familyMemberSecondary and relationshipType have values of 0, where I
was expecting the id's of each of these would be passed over.
Because you are using EditorFor helper method for familyMemberPrimary property in your view. So if you are not filling a value in that input field, it is going to have default value(0 for int type)
If you want that property to be filled with your dropdown selection(of family members), you should give the dropdown name value as familyMemberPrimary so that when you post the form, model binding will set the selected option value to familyMemberPrimary property.
#Html.DropDownList("familyMemberPrimary",
ViewBag.familyMember as IEnumerable<SelectListItem>,
htmlAttributes: new { #class = "form-control" })
I already saw a bunch of these posts but none helped me because most weren't applied to C# and MVC.
I have the Create for an object named TipoImovel. This object has an auto-generated ID (int), a description (string - tipoImovel) and then the possibly NULL value to a sub-TipoImovel (int? and then the reference TipoImovel). If it's confusing think of it like you create a House (TipoImovel). You can then create a Pool (also TipoImovel) and say it's a sub-type of TipoImovel making it a Pool which is subTipoImovel of House (House with pool). Sorry for the names but they are in my native language. If any questions arise around them please say.
Now here's the code:
TipoImovel.cs
public class TipoImovel
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[Display(Name = "Tipo de Imóvel")]
[StringLength(20)]
public string tipoImovel { get; set; }
[Display(Name = "Sub-Tipo de:")]
public int? tipoImovelID { get; set; }
[Display(Name = "Sub-Tipo de:")]
public virtual TipoImovel subTipoImovel { get; set; }
}
TipoImovelController.cs (GET and POST methods)
// GET: TipoImovel/Create
public ActionResult Create()
{
ViewBag.tipoImovelID = new SelectList(db.TipoImovel, "ID", "tipoImovel");
return View();
}
// POST: TipoImovel/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ID,tipoImovel,tipoImovelID")] TipoImovel TipoImovel)
{
if (ModelState.IsValid)
{
db.TipoImovel.Add(TipoImovel);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.tipoImovelID = new SelectList(db.TipoImovel, "ID", "tipoImovel", TipoImovel.tipoImovelID);
return View(TipoImovel);
}
Create.cshtml
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>TipoImovel</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.tipoImovel, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.tipoImovel, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.tipoImovel, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.tipoImovelID, "tipoImovelID", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("tipoImovelID", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.subTipoImovel, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
The error comes up because the TipoImovel I receive in the POST method comes as NULL. The form is properly created retreiving any (manually introduced) existing TipoImovel and showing them in the ComboBox but upon hitting "Create" it crashes.
I've been around this problem for 2 days and I can't fix it. Any help is appreciated!
EDIT: Pic of generated HTML:
Your model has a property string TipoImovel but you have also named the parameter of your POST method TipoImovel (and even more confusing, your class is also named TipoImovel)
Change the name of the parameter so that it dos not match one of the properties of your model, say
public ActionResult Create(TipoImovel model)
{
....
}
I am very new to MVC and have just added a cascading drop down to my create page so when a Practice is selected the Optician drop down is populated with the names of opticians that work at that practice.
Model:
public class Booking
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid BookingId { get; set; }
[ForeignKey("Patient")]
public Guid PatientId { get; set; }
public virtual Patient Patient { get; set; }
public IEnumerable<SelectListItem> PatientList { get; set; }
[ForeignKey("Practice")]
public Guid PracticeId { get; set; }
public virtual Practice Practice { get; set; }
public IEnumerable<SelectListItem> PracticeList { get; set; }
[ForeignKey("Optician")]
public Guid OpticianId { get; set; }
public virtual Optician Optician { get; set; }
public IEnumerable<SelectListItem> OpticiansList { get; set; }
[Display(Name = "Date")]
[DataType(DataType.Date)]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:dd/MM/yyyy}")]
public DateTime Date { get; set; }
[ForeignKey("Time")]
public Guid? TimeId { get; set; }
public virtual Time Time { get; set; }
public IEnumerable<SelectListItem> TimeList { get; set; }
public bool isAvail { get; set; }
}
My Controller:
// GET: Bookings1/Create
public ActionResult Create()
{
var practices = new SelectList(db.Practices, "PracticeId", "PracticeName");
ViewData["Practice"] = practices;
Booking booking = new Booking();
ConfigureCreateModel(booking);
return View(booking);
}
public void ConfigureCreateModel(Booking booking)
{
booking.PatientList = db.Patients.Select(p => new SelectListItem()
{
Value = p.PatientId.ToString(),
Text = p.User.FirstName
});
booking.TimeList = db.Times.Select(t => new SelectListItem()
{
Value = t.TimeId.ToString(),
Text = t.AppointmentTime
});
}
// POST: Bookings1/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Booking booking)
{
// to ensure date is in the future
if (ModelState.IsValidField("Date") && DateTime.Now > booking.Date)
{
ModelState.AddModelError("Date", "Please enter a date in the future");
}
// Sets isAvail to false
booking.isAvail = false;
//Checks if model state is not valid
if (!ModelState.IsValid)
{
ConfigureCreateModel(booking);
return View(booking); // returns user to booking page
}
else // if model state is Valid
{
// Generates a new booking Id
booking.BookingId = Guid.NewGuid();
// Adds booking to database
db.Bookings.Add(booking);
// Saves changes to Database
db.SaveChanges();
// Redirects User to Booking Index
return RedirectToAction("Index");
}
}
My View:
<script src="~/Scripts/jquery-1.10.2.js"></script>
<script>
$(document).ready(function () {
$("#Optician").prop("disabled", true);
$("#Practice").change(function () {
$.ajax({
url : "#Url.Action("Opticians","Bookings")",
type : "POST",
data : {Id : $(this).val() }
}).done(function (opticianList) {
$("#Optician").empty();
for (var i = 0; i < opticianList.length; i++) {
$("#Optician").append("<option>" + opticianList[i] + "</option>");
}
$("#Optician").prop("disabled", false);
});
});
});
</script>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Booking</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.PatientId, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.PatientId, Model.PatientList, "-Please select-", new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.PatientId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.PracticeId, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("Practice", ViewData["Practice"] as SelectList,"-Please Select-", new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.PracticeId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.Label("Select Optician :", new { #class = "col-md-2 control-label" })
<div class="col-md-10">
<select id="Optician"></select>
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Date, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Date, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Date, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.TimeId, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.TimeId, Model.TimeList, "-Please select-", new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.TimeId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
The cascading drop down works as it should how ever when I click the button to create the appointment the following exception is thrown:
Exception:
An exception of type 'System.InvalidOperationException' occurred in System.Web.Mvc.dll but was not handled in user code
Additional information: There is no ViewData item of type 'IEnumerable' that has the key 'Practice'.
Any help would be greatly appreciated.
Thanks
You model already contains a property for the collection of practices
public IEnumerable<SelectListItem> PracticeList { get; set; }
although it should not contain
public virtual Practice Practice { get; set; }
In the GET method, you create a new SelectList for practices, but instead of assigning it to the model property, you add it to ViewData using
ViewData["Practice"] = practices;
and then in the view use
#Html.DropDownList("Practice", ViewData["Practice"] as SelectList, ..)
which is not even binding to a property in your model and would never post back to anything. Then when you return the view in the POST method (because your mode will always be invalid), you do not assign a value to ViewData["Practice"] so its null, hence the error.
Instead, in your ConfigureCreateModel() method, populate the PracticeList property (as your doing for PatientList) and remove the use of ViewData, and in the view use
#Html.DropDownListFor(model => model.PracticeId, Model.PracticeList, ...)
so your strongly binding to your model and when your submit the form, the value of PracticeId will be the value of the selected practice.
Side note: You will need to change your script to $("#PracticeId").change(function () { ...
simnilar to the answer of this question
Html.BeginForm with html attributes asp.net mvc4
I have a viewmodel for a view that contains collections that are used to populate drop downs and lists. so i dont watn to return them, i just want to return the model object. Well actually i just want to return 4 fields in that model - but that's the next problem.
I've dodged that rpeviously by doing this appraoch but im having no luck unless i submit the entire viewmodel which on this form is ridiculous as 95% of info is discarded.
Anyway the problem i get here is that i cannot get the game event that is returned in the create post to be anything other than null. The gameEvent parameter on create is NULL.
Also kinda suprised i haven't been able to find a ton of info on this.
The controller:
public ActionResult Create()
{
...
var createEventViewModel = new CreateEventViewModel()
{
Places = places,
Characters = characters,
Event = new GameEvent()
};
return this.View(createEventViewModel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Name,Description,EventType,Duration")] GameEvent gameEvent)
{
...
}
The View:
#model Sisyphus.Web.Models.CreateEventViewModel
#{
ViewBag.Title = "Create Event";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Create Event</h2>
<div class="row">
<div class="col-lg-8">
<section id="createEvent">
#using (Html.BeginForm("Create", "Event",
new
{
GameEvent = Model.Event
}, FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(m => m.Event.Name, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.Event.Name, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Event.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.Event.Description, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextAreaFor(m => m.Event.Description, 10, 30, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Event.Description, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Event.Duration, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.Event.Duration, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Event.Duration, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Event.EventType, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EnumDropDownListFor(m => m.Event.EventType)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create Event" class="btn btn-default" />
</div>
</div>
}
</section>
</div>
</div>
The Model:
public class GameEvent
{
public string Name { get; set; }
public string Description { get; set; }
public int Duration { get; set; }
public EventType EventType { get; set; }
}
The viewmodel: (edited down have removed members that are irrelevant
public class CreateEventViewModel
{
public GameEvent Event { get; set; }
}
Edit:
Ok i just tried this
#using (Html.BeginForm("Create", "Event",
new RouteValueDictionary()
{
{"GameEvent", Model.Event}
}, FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
Game event is now not null (All values in it are) - so not really any closer
Your inputs for postback are based on class CreateEventViewModel, for example
#Html.TextBoxFor(m => m.Event.Name, ...
#Html.TextAreaFor(m => m.Event.Description, ...
which would generate the following html
<input id="Event_Name" name="Event.Name" value=....
However the parameter of your post action method is typeof GameEvent, not CreateEventViewModel. If you inspect the Response.Form.Keys you will see Event.Name, Event.Description etc, but class GameEvent has properties Name, Description etc so the values cant be matched up by the ModelBinder
You need to change your post method to
public ActionResult Create(CreateEventViewModel model)
{
GameEvent event = model.GameEvent;
// do whatever with GameEvent
You should also remove new {GameEvent = Model.Event} from theHtml.BeginForm` method
Note I excluded the BindAttibute because I don't think its necessary in this case - you appear to want all the properties of GameEvent, and unless you create inputs for properties of Places and Characters, they will be null anyway, and since you are not accessing the other properties there is no mass assignment vulnerability.
Other alternative are to create the inputs manually so that the properties are correctly mapped, either direct html
<input name="Name" value=#Model.Event.Name />
<input name="Description" value=#Model.Event.Desciption />
or using helpers
var Description = Model.Event.Description;
#Html.TextBoxFor(m => Description)