Reputation: 73
Is it possible to enable model binding along with a posted data from a form?
I have a collection property that I want to iterate in a foreach
loop to save each selected item in the collection:
<div class="form-group">
@Html.LabelFor(m => m.Users, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@{
List<ApplicationUser> allUsers = ViewBag.AllUsers;
bool assigned;
}
<div>
@foreach (var user in allUsers)
{
//here I want to render all users, and only the users who are in the task, have a checked checkbox
assigned = Model.Users.Select(u => u.Id).Contains(user.Id);
<input type="checkbox" name="asndUsers" value="@user.Id" id="@user.Id" @Html.Raw(assigned ? "checked" : "") /> <label style="font-weight: normal;" for="@user.Id">@user.UserName</label><br />
}
</div>
</div>
</div>
//fields updated with model binding:
<div class="form-group">
@Html.LabelFor(m => m.Status, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(m => m.Status, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(m => m.Status)
</div>
</div>
this is the Edit action post method:
[HttpPost, ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "Id,Title,Description,DueDate,Status")] UserTask task, string[] asndUsers)
{
if (ModelState.IsValid)
{
task.Users = new List<ApplicationUser>();
foreach (var item in asndUsers)
{
var user = context.Users.Find(item);
task.Users.Add(user);
}
context.Entry(task).State = EntityState.Modified;
context.SaveChanges();
return RedirectToAction("Index");
}
return View(task);
}
it works when I debug, I see the new posted data has merged with the bound data. but when the request redirected to the Index view, there is no change after editing an item, this is the Index action method:
public ActionResult Index(int? taskId)
{
var viewModel = new TasksUsers();
viewModel.Tasks = context.Tasks.Include(x => x.Users);
if (taskId != null)
{
viewModel.Users = viewModel.Tasks.Where(t => t.Id == taskId).Single().Users;
ViewBag.Row = taskId;
}
return View(viewModel);
}
Upvotes: 0
Views: 295
Reputation: 73
Original answer
The proper way of updating the related entities is loading them first, so I used eager loading to redefine the incoming
task
parameter as follows:task = context.Tasks.Include(t => t.Users).Single(s => s.Id == task.Id); Note that `Find` can't be used with `Include` so I used
Single
.That resolved the problem of updating the Users entity
this was wrong,
the proper way is to use explicit binding instead of implicit binding (TryUpdateModel()
)
Upvotes: 1
Reputation: 9463
The task that is posted back is no longer tracked by the DbContext. Try to Attach
the task to the DbSet in the Edit action:
context.Tasks.Attach(task);
if (task.Users == null) {
task.Users = new List<ApplicationUser>();
}
foreach (var item in asndUsers) {
var user = context.Users.Find(item);
task.Users.Add(user);
}
// may be important because Attach() sets the State to 'Unchanged'?
context.Entry(task).State = EntityState.Modified;
context.SaveChanges();
As a side note, you can pass parameters when you call RedirectToAction
. (Only do this if you want to pass the id of the edited task to the Index action):
return RedirectToAction("Index", new { taskId = existingTask.Id });
// ^^^^^^
// must match parameter name of Index action
Upvotes: 0