Invalid Operation Exception MVC C#

2019-06-14 17:36发布

问题:

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.