asp.net mvc 5 - Passing a ViewBag item back to the controller - c#

I am trying to pass a ViewBag item from my view back into the post method for that view but I am having difficulty.
This is a simplified version of my methods:
public ActionResult CreateGame(int id)
{
var selPlayer = db.Players.Find(id);
if (selPlayer == null)
{
return HttpNotFound();
}
ViewBag.SelPlayerId = selPlayer.PlayerID;
PlayerGame newGame = new PlayerGame ();
return View(newGame);
}
[HttpPost]
public ActionResult CreateGame(PlayerGame newGame)
{
newGame.GameTitle = newGame.GameTitle;
newGame.GameNotes = newGame.GameNotes;
newGame.PlayerID = newGame.PlayerID;
return RedirectToAction("PlayerView");
return View(newGame);
}
This is a simplified version of my View:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<fieldset>
<div class="editor-label">
#Html.LabelFor(model => model.GameTitle)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.GameTitle)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.GameNotes)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.GameNotes)
</div>
#Html.HiddenFor(model => model.PlayerID, new { PlayerID = ViewBag.SelPlayerId })
<p>
<input type="submit" value="Submit Game" class="btn btn-info" />
</p>
</fieldset>
</div>
}
In the POST method you can see I am trying to set the PlayerID equal to the player Id that I am passing through as ViewBag.SelPlayerID but when I look at the values that are being passed through in the debugger the id is always equal to 0 which is not right as the player I am using to test has an id of 1.
My question is how can I set the player ID for the new game I am creating equal to the player ID I am passing through as the ViewBag item, or alternatively if there is an easier way of doing this that I have overlooked.
Note: The player id is a foreign key in my Player Game model:
//Foreign Key for Player
public int PlayerID { get; set; }
[ForeignKey("PlayerID")]
public virtual Player Player { get; set; }

You usage of new { PlayerID = ViewBag.SelPlayerId } in the #Html.HiddenFor() method is setting a html attribute, not the value of the property.
If you inspect the html your generating it will be
<input type="hidden" name="PlayerID" PlayerID="1" value="0" />
Change your GET method to set the value of the property in the model (and delete the ViewBag property)
PlayerGame newGame = new PlayerGame() { PlayerID = selPlayer.PlayerID };
return View(newGame);
and in the view use
#Html.HiddenFor(m => m.PlayerID)

This:
#Html.HiddenFor(model => model.PlayerID, new { PlayerID = ViewBag.SelPlayerId })
Is creating a hidden element, bound to model.PlayerID, but with a custom Html attribute of PlayerID, you are getting 0 as thats the default value for int and you are not setting it on the model.
Based on your (simplified) code sample, you should be able to set the model PlayerID to your selected value in the get action on the controller:
newGame.PlayerID = selPlayer.PlayerID
And then
#Html.HiddenFor(m => m.PlayerID)
If for some reason you have to use ViewBag, and can't populate it on the model as above you can use #Html.Hidden instead to ensure the value comes back in.
#Html.Hidden("PlayerID", ViewBag.SelPlayerId)
Will result in:
<input type="PlayerID" value="1" />
Which should model bind back to the model. If that's not an option and to do it more manually, you can change the first parameter to something like "SelectedPlayerID" and then you can either pull out in from Request in the controller post action.

Just Try this using viewbag alone.Set hidden type element in cshtml view page.On postback you will get your player id in your argument.This will work out if you are using input type submit
<input type="hidden" id="PlayerID" name="PlayerID" value="#ViewBag.SelPlayerId"/>

Related

ASP.NET MVC one particular view model property becoming null on returning to view

In ASP.NET MVC I have a view that's a form, and I want to be able to save the form and then it returns back to the same page showing the data you entered after saving the data to the database. I'm sure I'm just doing something stupid (this is pretty new to me), but there are some properties that I want to persist and I am setting them on the view model before I return, and I have #Html.HiddenFor inside my view's form. My confusion is that of these items are retained, and some aren't. So I have the following inside my FormController (methods and names have been simplified for brevity):
public ActionResult Index(int? p, int? c)
{
FormViewModel model = new FormViewModel();
model.p = p;
model.c = c;
model.dateStarted = DateTime.Now;
return View(model);
}
[HttpPost]
public ActionResult Index(FormViewModel m)
{
Form form;
bool shouldUpdate = false;
if (m.formID != null) // m.formID is always null, but m.p, c, dateStarted aren't
{
shouldUpdate = true;
form = getFormnWithId((int)m.formID); //gets from database
}
else
{
form = new Form(m);
}
if (shouldUpdate)
{
editForm(form); //edit existing entry
}
else {
addForm(form); //add to database
}
m.formID = form.Id; // formn.Id is valid because the form has been updated with its Id after being added to the database
m.p = form.p;
m.c = form.c;
return View(m);
}
Inside my view (cshtml) file I have #Html.HiddenFor(model=>model.formID) as well as for other properties I want to persist but aren't being set in the form directly.
The formID however, is not persisting, while the other items (represented by c and p and dateStarted) are fine. If I remove the HiddenFor for those other fields, then they don't work. I click save each time, and formID is null in the post, but it's definitely set after the form has been added to the database and the value of the formID is definitely getting sent to the view. I just don't understand why it comes back null but the other properties don't.
Here's what the model looks like:
public class FormViewModel
{
public Nullable<int> formID {get; set;}
public Nullable<int> c { get; set; }
public Nullable<int> p { get; set; }
public System.DateTime dateStarted { get; set; }
//+ other form properties
}
View:
...
<label for="submit-form" class="btn btn-default">Save</label>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal col-md-12">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
<!-- various form fields -->
</div>
#Html.HiddenFor(model => model.dateStarted)
#Html.DisplayFor(model => model.dateStarted)<br /> <!-- just to see it while testing-->
#Html.HiddenFor(model => model.c)
#Html.DisplayFor(model => model.c)<br />
#Html.HiddenFor(model => model.p)
#Html.DisplayFor(model => model.p)<br />
#Html.HiddenFor(model => model.formID)
#Html.DisplayFor(model => model.formID)
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" name="Command" class="btn btn-default hidden" id="submit-form" />
</div>
</div>
</div>
}
Now that I see you are setting the Form.Id in the POST request, your issue is that you are not following the PRG (Post, Redirect, Get) pattern. You are returning the same view from your POST method without any type of redirect. As a result, the model binder is holding on to the previous value of Form.Id, which was null. The reason that the model binder holds on to previous values is mainly for validation purposes (if the ModelState has an error, you can return the view, the properties remain as the user entered them along with the ModelState errors collection)
To fix this, you either need to redirect to another action or issue ModelState.Clear() in your code before you return the view.
m.formID = form.Id; // form.Id is valid because the form has been
//updated with its Id after being added to the database
m.p = form.p;
m.c = form.c;
ModelState.Clear();
return View(m);

View with multiple models only POST 1 model

This is the model I'm using for a view
public class MainRegisterViewModel
{
public RegisterViewModel RegisterModel { get; set; }
public RegisterFormValuesViewModel RegisterValues { get; set; }
}
RegisterFormValuesViewModel contains all the values for the controls (list of countries, states and stuff like that) and RegisterViewModel contains the information for a user.
Then I load the controls like this.
#model ProjetX.Models.MainRegisterViewModel
#{
IEnumerable<SelectListItem> countries = Model.RegisterValues.Countries.Select(x => new SelectListItem()
{
Text = x.Country,
Value = x.Id.ToString()
});
IEnumerable<SelectListItem> states = Model.RegisterValues.States.Select(x => new SelectListItem()
{
Text = x.State,
Value = x.Id.ToString()
});
}
<div class="container">
<div class="row">
<div class="col-md-12">
#using (Html.BeginForm("Register", "Account", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
....
<div class="form-group">
#Html.LabelFor(m => m.RegisterModel.Country, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.DropDownListFor(m => m.RegisterModel.Country, countries, new { #class = "form-control" })
</div>
</div>
....
Also the Register function takes a MainRegisterViewModel as parameter.
public async Task<ActionResult> Register(MainRegisterViewModel model)
The problem is when I submit the form, RegisterFormValuesViewModel is NULL.
Any ideas why?
Thank you
Context
I'm doing this because I load the RegisterFormValuesViewModel from an API and I'm trying to call it only once. The problem was when a user POST a form with errors and you return the view back, I had to call the API again to get RegisterFormValuesViewModel.
Before this it was only one model and a viewbag for RegisterFormValuesViewModel but I had to call the API every time the form was loaded because the viewbag wasn't posted. That's why I thought I could use 2 models and POST them both.
If you want the values of RegisterFormValuesViewModel to be posted, they need to included in the form or other location that the ModelBinder looks for values. The (default) model binder will pick up values from the action params, Request.Form, route data, and Request.QueryString (I think Request.Files is included too).
If your RegisterFormValuesViewModel is expensive to create, you can add it's values as hidden fields that are posted with the form or implement a custom ValueProviderFactory that works with application or session state.

MVC CheckboxFor never passing view correct value? Binding checkboxFor

I have a MVC project im currently working on and i have a check box in one of my views. When i change the check box it never passes the correct value to my modal?
View CheckboxFor:
#model OBASA.Models.EmployeeModal
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>EmployeeModel</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Active)
</div>
<div class="editor-field">
#Html.CheckBoxFor(model => model.Active)
#Html.ValidationMessageFor(model => model.Active)
</div>
<p>
<input type="submit" name="Sub" value="Save" />
</p>
</fieldset>
}
Modal:
public class EmployeeModal
{
public bool Active { get; set; }
}
In run time when i check or un check the check box it never passes the value, its always false.
Please can someone tell me how to bind this correctly for MVC.
The code you've posted is correct, though you have not posted your controller code. It needs to look something like this:
public ActionResult Junk()
{
return View();
}
[HttpPost]
public ActionResult Junk(EmployeeModal model)
{
bool bActive = model.Active;
return View(model);
}

Model Key and Foreign Key parametres are null in controller after POST action

I have the following hidden fields inside my View:
#Html.HiddenFor(model => model.cat.Id)
#Html.HiddenFor(model => model.cat.OwnerId)
#Html.HiddenFor(model => model.cat.BirthDate
#Html.HiddenFor(model => model.cat.Weight)
Id is a Key and OwnerId is a ForeignKey. When I try to edit that model, all values are displayed correctly but the problem is when I try to POST those values back. Id and OwnerId are always 0. I have a lot of other values inside my view like dropdown lists, text boxes etc, all those values are posted correctly. The problem is only with Id and OwnerId.
Controller:
[HttpPost]
public ActionResult Edit(EditCatDetailsViewModel model)
{
Debug.WriteLine("Cat Id: " + model.cat.Id); //displays 0
}
I checked source of web site and I see Id and OwnerId have correct values (values aren't 0). My question is why are those values not sent like all other values? Why are they received in controller as 0?
Thank you!
UPDATE:
Cat Edit View:
#model Pro.Web.Models.EditCatDetailsViewModel
#{
ViewBag.Title = "Edit";
}
<h2>Edit Cat Details</h2>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Cat</legend>
#Html.HiddenFor(model => model.cat.Id)
#Html.HiddenFor(model => model.cat.OwnerId)
#Html.HiddenFor(model => model.cat.BirthDate
#Html.HiddenFor(model => model.cat.Weight)
<div class="editor-label">
#Html.LabelFor(model => model.cat.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.cat.Name)
#Html.ValidationMessageFor(model => model.cat.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.cat.Description)
</div>
<div class="editor-field">
#Html.TextAreaFor(model => model.cat.Description, new { #style="width:400px; height:600px" })
#Html.ValidationMessageFor(model => model.cat.Description)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.cat.NumberOfKittens)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.cat.NumberOfKittens)
#Html.ValidationMessageFor(model => model.cat.NumberOfKittens)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to Details", "Details")
</div>
At the end I managed to do it like this. I put additional variable inside my EditCatDetailsViewModel which represents a cat id.
This is simplified look of that model now:
public class EditCatDetailsViewModel
{
public Cat cat { get; set; }
public int catId { get; set; }
}
Inside my GET Edit method I've set that Id from Id in Cat model:
// GET: /Cats/Edit/5
public ActionResult Edit(int id = 0)
{
Cat cat = _catRepository.Find(id);
if (cat == null)
{
return HttpNotFound();
}
EditCatDetailsViewModel model = new EditCatDetailsViewModel();
model.cat = cat;
model.catId = cat.Id;
return View(model);
}
And in my View I added additional hidden element:
#Html.HiddenFor(model => model.catId)
Now I can access that Id in POST method:
[HttpPost]
public ActionResult Edit(EditCatDetailsViewModel model)
{
Debug.WriteLine("Cat Id: " + model.catId); //displays correct Id for Cat!!!
model.cat.Id = model.catId;
}
Now it all works and I can save changes to my DB. I am still interested why those values were not posted the first time.
Hi Its seems that hidden field generated via razor does not updates their values on posting them back again. So please try to make your hidden fields via html syntex not with razor like this:
<input type="hidden" id="" name="" value="#Model.model.cat.Id" />
<input type="hidden" id="" name="" value="#Model.model.cat.OwnerId" />
Check your page source and copy the value of name attribute of these hidden fields from there and assign them here and now check I hope you will get those values also in your action method.

MVC4 Complex Type Model is null after post

This is my model
public class AdministrationModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string EmailAddress { get; set; }
public bool IsApproved { get; set; }
}
This is my controller
public ActionResult GetTabContent(string id)
{
switch (id)
{
case "tab3":
model = GetAllUsersInfo();
viewName = "Administration";
break;
}
return View(viewName);
}
private List<AdministrationModel> GetAllUsersInfo()
{
List<AdministrationModel> userList = new List<AdministrationModel>();
foreach (MembershipUser user in Membership.GetAllUsers())
{
UserProfile userProfile = UserProfile.GetUserProfile(user.UserName);
userList.Add(new AdministrationModel { EmailAddress = user.Email, IsApproved = user.IsApproved, FirstName = userProfile.FirstName, LastName = userProfile.LastName });
}
return userList;
}
This is my View
#model List<AdminContainerModel>
#using (Html.BeginForm("Administration", "Account"))
{
<fieldset>
<div>
#foreach (AdministrationModel AM in Model)
{
<div>
<div class="colFull">#Html.DisplayFor(modelItem => AM.FirstName)</div>
<div class="colFull">#Html.DisplayFor(modelItem => AM.LastName)</div>
<div class="colFull">#Html.DisplayFor(modelItem => AM.EmailAddress)</div>
<div class="colPartial"><input type="checkbox" checked="#AM.IsApproved"/> </div>
<div class="clear"></div>
</div>
}
</div>
<input type="submit" value="Update Account" />
</fieldset>
}
When the user clicks the Update Account button it goes to the controller
[HttpPost]
public ActionResult Administration(List<AdministrationModel> model)
{
return View();
}
inside this method, model is always null. however the View that renders everything is perfect and shows what I want it to show. What am I doing wrong?
When using collections, in order to correctly process them so they are model-bound on post without any additional leg work, you need to make sure they are indexed correctly, you can do this by using a for loop, something like:
#for (int i = 0; i < Model.Count; i++)
{
#Html.HiddenFor(m => m[i].FirstName)
#Html.HiddenFor(m => m[i].LastName)
#Html.HiddenFor(m => m[i].EmailAddress)
<div>
<div class="colFull">#Html.DisplayFor(m => m[i].FirstName)</div>
<div class="colFull">#Html.DisplayFor(m => m[i].LastName)</div>
<div class="colFull">#Html.DisplayFor(m => m[i].EmailAddress)</div>
<div class="colPartial">#Html.CheckBoxFor(m => m[i].IsApproved)</div>
<div class="clear"></div>
</div>
}
That should model bind without any other code :)
Edit: Sorry I forgot, displayFors by default don't put the correct properties on for model binding, added hiddenFors for the other fields that don't have an editorFor
Edit2: Based on your other question in the comment, if it was public facing and you didn't want them to change any of the hidden for values using the dev tools, try the following:
Ok so you don't want them to change the hiddenFors, that's fine, but you will need some sort of ID so you know which client is which when the data is posted, I suggest that instead of having these in the above code:
#Html.HiddenFor(m => m[i].FirstName)
#Html.HiddenFor(m => m[i].LastName)
#Html.HiddenFor(m => m[i].EmailAddress)
Replace them with:
#Html.HiddenFor(m => m[i].ClientId)
That way you're not posting back the firstname, lastname or email address, just a reference to the actual client that is ticked, unticked.
To answer your other question in the comment about keeping track of the original values, in your controller method you can just go and get the original values from the database, then here's how you can detect which ones are different, something like:
[HttpPost]
public ActionResult Administration(List<AdministrationModel> model)
{
var originalMatches = GetAllUsersInfo();
var differences = (from o in originalMatches
join c in model on o.ClientId equals c.ClientId
where c.IsApproved != o.IsApproved).ToList()
return View();
}
Your #model directive is incorrect, it should be the fully qualified type name.
In this case:
#model TheNamespace.AdministrationModel
After your updated question.
Try using:
#Html.EditorFor(Model)
This will call a specialized Editor Template for your list.
http://blogs.msdn.com/b/nunos/archive/2010/02/08/quick-tips-about-asp-net-mvc-editor-templates.aspx
If you want to return list items with the model on submit you will need to use a for loop, not a foreach
Matty's answer will work.
If you want to avoid having to specify indexes (which wouldn't work if you had an ICollection) you can define a child template as Xander has explained.
The benefit is you still get all your strong typed intellisense in the AdminContainerModel View and MVC hooks the items back in to your List on post back for you out of the box.
Here's an example:
Main Editor template (as Xander said):
#model IEnumerable<AdminContainerModel>
#using (Html.BeginForm("Administration", "Account"))
{
<fieldset>
<div>
#Html.EditorForModel()
</div?
</fieldset>
}
AdminContainerModel Editor template(This is called for each item in your List because you've called #Html.EditorForModel:
#model AdminContainerModel
<div>
<div class="colFull">#Html.DisplayFor(modelItem => AM.FirstName)</div>
<div class="colFull">#Html.DisplayFor(modelItem => AM.LastName)</div>
<div class="colFull">#Html.DisplayFor(modelItem => AM.EmailAddress)</div>
<div class="colPartial"><input type="checkbox" checked="#AM.IsApproved"/>
</div>
<div class="clear"></div>

Categories