问题
I am very new to c# and MVC. I am trying to create a web application to make bookings for an Opticians practice. I have created my database using Code-First Entity Framework. On the create booking page I have a drop down where an Optician can be selected. When I created the create page using CRUD, the drop down showed the UserId, however I am trying to change that to a concat(FullName) of the Application Users(Optician) FirstName and LastName.
Booking 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; }
[ForeignKey("Practice")]
public Guid PracticeId { get; set; }
public virtual Practice Practice { get; set; }
[ForeignKey("Optician")]
public Guid OpticianId { get; set; }
public virtual Optician Optician { 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 bool isAvail { get; set; }
}
Identity Model:
public class ApplicationUser : IdentityUser
{
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Display(Name = "Last Name")]
public string LastName { get; set; }
public string FullName
{
get
{
return this.FirstName + " " + this.LastName;
}
}
Optician:
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid OpticianId { get; set; }
[ForeignKey("User")]
public string UserId { get; set; }
public virtual ApplicationUser User { get; set; }
[ForeignKey("Practice")]
public Guid PracticeId { get; set; }
public virtual Practice Practice { get; set; }
public virtual ICollection<Booking> Bookings { get; set; }
public virtual ICollection<Appointment> Appointments { get; set; }
}
Booking Controller:
// GET: Bookings/Create
public ActionResult Create()
{
ViewBag.OpticianId = new SelectList(db.Opticans, "OpticianId", "UserId");
ViewBag.PatientId = new SelectList(db.Patients, "PatientId", "HCN");
ViewBag.PracticeId = new SelectList(db.Practices, "PracticeId", "PracticeName");
ViewBag.TimeId = new SelectList(db.Times, "TimeId", "AppointmentTime");
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "BookingId,PatientId,PracticeId,OpticianId,Date,TimeId,isAvail")] 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;
if (ModelState.IsValid)
{
booking.BookingId = Guid.NewGuid();
db.Bookings.Add(booking);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.OpticianId = new SelectList(db.Opticans, "OpticianId", "FullName", booking.Optician.User.FullName);
ViewBag.PatientId = new SelectList(db.Patients, "PatientId", "HCN", booking.PatientId);
ViewBag.PracticeId = new SelectList(db.Practices, "PracticeId", "PracticeName", booking.PracticeId);
ViewBag.TimeId = new SelectList(db.Times, "TimeId", "AppointmentTime", booking.TimeId);
return View(booking);
}
Create View:
<div class="form-group">
@Html.LabelFor(model => model.Optician.User.FullName, "FullName", htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DropDownList("FullName", null, htmlAttributes: new { @class = "form-control" })
@Html.ValidationMessageFor(model => model.Optician.User.FullName, "", new { @class = "text-danger" })
</div>
</div>
I am getting the following 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 'FullName'
Any help would be greatly appreciated
回答1:
The reason you get that error is because property FullName is a string, not IEnumerable<T> and in the view you use
@Html.DropDownList("FullName", null, ...)
If you provide null as the second parameter then the first parameter must be IEnumerable<T> where T is a value type or SelectListItem.
In any case FullName is a readonly property (does not have a setter) so it would never bind to your property anyway, not to mention you exclude the property from binding by you awful use of the [Bind(include = "...")] attribute (if you ever find yourself using it, then use a view model instead).
Its unclear what your actually wanting to do since your SelectList constructors don't make sense. new SelectList(db.Opticans, "OpticianId", "UserId"); means you generating <option> elements with a value attribute equal to the value of the OpticianId property, and a text equal to the value of the UserId property. But then you don't even use it in the view anyway.
Start by creating a view model representing what you want to display in the view
public class BookingVM
{
[Display(Name = "Optician")]
[Required(ErrorMessage = "Please select an optician")]
public Guid OpticianId { get; set; } // recommend you use int , not Guid
public IEnumerable<SelectListItem> OpticianList { get; set; }
.... // other properties to edit in the view
}
Then in the controller
public ActionResult Create()
{
BookingVM model = new BookingVM();
ConfigureCreateModel(BookingVM model);
return View(model);
}
// common code for initializing select lists etc
public void ConfigureCreateModel(BookingVM model)
{
model.OpticianList = db.Opticans.Select(o => new SelectListItem()
{
Value = o.OpticianId,
Text = o.User.FullName
}
.... // other select lists
}
and in the view
@model BookingVM
....
<div class="form-group">
@Html.LabelFor(m => m.OpticianId, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DropDownListFor(m => m.OpticianId, Model.OpticianList, "-Please select-", new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.OpticianId, new { @class = "text-danger" })
</div>
</div>
and finally in the post method
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(BookingVM model)
{
if (!ModelState.IsValid)
{
ConfigureCreateModel(model);
return View(model);
}
// map you view model properties to a new instance of your data model
// save the data model and redirect.
}
Side note: You view model could include a DateTime MinDate property (include as a hidden input in the view), then apply a foolproof [GreaterThan] or similar attribute to your Date property so you get both client and server side validation out of the box. A jquery datepicker plugin that limits the dates that can be selected would also improve user experience.
来源:https://stackoverflow.com/questions/32170184/invalid-operation-exception-mvc-c-sharp