James Wood James Wood - 1 month ago 6
ASP.NET (C#) Question

DropDownListFor "the value is invalid" when setting navigation property

I am attempting to set a navigation property (foreign key) based on the return value from a DropDownList.

I have the following data model:

(Some properties and details omitted for the sake of brevity).

An invite, which has a Guid Id and a collection of guests.

public class Invite
{
public Guid InviteId { get; set; }

public virtual ICollection<Guest> Guests { get; set; }
}


A guest, with an Invite property linking it to invite.

public class Guest
{
public virtual Invite Invite { get; set; }
}


In my DataInitaliser I can correctly build these objects and they are sent to the database, for example:

new Invite()
{
InviteId = Guid.NewGuid(),
Name = "Bloggs",
AllDay = false,
Guests = new List<Guest>()
{
new Guest() { GuestId = Guid.NewGuid(), FirstName = "Joe", LastName = "Bloggs", Vip = false},
}
};


In my GuestController I build the list of possible invites and add it to the ViewBag for presenting in the view.

void PopulateInvite(object invite)
{
var query = db.Invites.Select(i => i).OrderBy(i => i.Name).ToList();

ViewBag.Invites = new SelectList(query, "InviteId", "Name", invite);
}


I present the list of objects in the Guest View like so:

@model Models.Guest
<div class="form-group">
@Html.LabelFor(model => model.Invite, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DropDownListFor(model => model.Invite, (IEnumerable<SelectListItem>)ViewBag.Invites, String.Empty)
@Html.ValidationMessageFor(model => model.Invite)
</div>
</div>


This correctly displays the expected values from the database.

The problem occurs when I post the values back to the GuestController.

enter image description here

The Post function for the create is pretty much the standard scaffold.

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create([Bind(Include="GuestId,FirstName,LastName,Vegetarian,Attending,Vip,Invite")] Guest guest)
{
if (ModelState.IsValid)
{
guest.GuestId = Guid.NewGuid();
db.Guests.Add(guest);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}

this.PopulateInvite(guest.Invite);
return View(guest);
}


I've dug into the cause a little bit here and I think I understand the underlying problem. My function
PopulateInvite
, places
InviteId
into the collection which is a Guid, this is returned as a String (not a Guid?) which cannot be converted into an Invite object.

"The parameter conversion from type 'System.String' to type 'Models.Invite' failed because no type converter can convert between these types."


I did try changing my PopulateInvite collection so its populated with an actual Invite object like so:

void PopulateInvite(object invite)
{
var query = db.Invites.Select(i => i).OrderBy(i => i.Name).ToList().Select(i =>
{
return new
{
Invite = new Invite() { InviteId = i.InviteId },
Name = i.Name
};
});

ViewBag.Invites = new SelectList(query, "Invite", "Name", invite);
}


However this also fails with the same error as above, confusingly I am returned a String representation of the object, instead of the actual object itself.

ModelState["Invite"].Value.RawValue
{string[1]}
[0]: "Models.Invite"


So...what is the correct way to set way to set the navigation property based on the post from the form?

Should I act before
ModelState.IsValid
to change the Guid into an actual Invite object?

As this tutorial from asp.net suggests, should I add a property to hold an InviteId, instead of using an invite object? In the sample
Department
is unused so I don't really understand why it has been added - am I missing something?

public class Course
{
public int DepartmentID { get; set; }

public virtual Department Department { get; set; }
}


Some other better method?

Answer

Well, the answer was in the actual tutorial I linked in the question. I needed to add an InviteId field to act as the foreign key then the actual object acts as the navigation property, as explained below both are required (not just one as I was using it).

Creating an Entity Framework Data Model for an ASP.NET MVC Application

The StudentID property is a foreign key, and the corresponding navigation property is Student. An Enrollment entity is associated with one Student entity, so the property can only hold a single Student entity (unlike the Student.Enrollments navigation property you saw earlier, which can hold multiple Enrollment entities).

The CourseID property is a foreign key, and the corresponding navigation property is Course. An Enrollment entity is associated with one Course entity.

Entity Framework interprets a property as a foreign key property if it's named (for example, StudentID for the Student navigation property since the Student entity's primary key is ID). Foreign key properties can also be named the same simply (for example, CourseID since the Course entity's primary key is CourseID).

Comments