List to Controller from View in MVC 4 missing values? - c#

After many years of getting a lot of great advice here, I finally have hit a wall teaching myself MVC4 ASP.net.
I used this post this post to pass a List of Type Class from my controller, to my view, and back to the controller..
public ActionResult SelectProducts()
{
displayProductsList = db.Products.ToList();
displayProductsList.ForEach(delegate(Product p)
{
//get list of recievables for the product
GetReceivablesByProductId(p.ProductID).ForEach(delegate(Receivable r)
{
//Get count of items in inventory for each recievable
p.CurrentInventory += this.CountItemsByReceivableID(r.RecievableID);
});
});
return View(FilterProductInventoryList(displayProductsList));
}
And here is my view code..
#model List<CarePac2.Models.Product>
#{
ViewBag.Title = "SelectProducts";
}
<h2>SelectProducts</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<table>
#*row values*#
#for (int i = 0; i < Model.Count; i++)
{
<tr>
<td>#Html.DisplayFor(m => m[i].Brand)</td>
<td>#Html.DisplayFor(m => m[i].ProductName)</td>
<td>#Html.DisplayFor(m => m[i].UnitType)</td>
<td>#Html.DisplayFor(m => m[i].SalePrice)</td>
<td>#Html.DisplayFor(m => m[i].CurrentInventory)</td>
<td>
#Html.EditorFor(m => m[i].OrderQuantity)
#Html.ValidationMessageFor(m => m[i].OrderQuantity)
</td>
<td></td>
</tr>
}
</table>
<p>
<input type="submit" value="Save" />
#*<input type="submit" value="Cancel" />*#
</p>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
Here the view show it has values for the List that was passed from controller to view..
Visually the view displays the data correctly (apparently i can't post an image of it until i have 10 reputation to post images)
when i hit submit and return to the controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult SelectProducts(List<Product> selectedProducts)
{
if (ModelState.IsValid)
{
}
}
The variable selectedProducts Is NOT NULL. The list has 3 Product items in it, however, as you can see in the image below in the debugger, even though i have 3 product items, none of the values exist from when the List of Product was originally passed to the view...
for example (since i can't post images yet):
selectedProducts[0].ProductID=0
selectedProducts[0].ProductName=null
selectedProducts[1].ProductID=0
selectedProducts[1].ProductName=null

You need to use #Html.HiddenFor():
#Html.HiddenFor(m => m[i].ProductID)
#Html.HiddenFor(m => m[i].ProductName)
This will send the data back to the controller. This creates an <input type="hidden"> that will be part of the form POST.

Related

MVC View with a form inside a For Loop submits empty model

I've seen a few very similar posts on here regarding iterating through a list of models and creating a form within each iteration, but nothing has led me to successfully POST back a populated model.
My goal is to present an inline form for each iteration of the model and allow users to make changes and save the edits for that specific model when the associated form gets submitted. I'm still fairly new to ASP.NET MVC so if there is a better way to go about this, please feel free to offer suggestions.
Thanks in advance for any help with this!
View
#model List<POSGuys.Option>
#{
var options = Model.OrderBy(i => i.OptionEndOfLife).ToList();
}
#for (int i = 0; i < options.Count(); i++)
{
using (Html.BeginForm("Save", "Option", FormMethod.Post ))
{
<tr style="#(options[i].OptionEndOfLife ? "color:#777" : "")">
#Html.HiddenFor(model => options[i].OptionID)
<td>#options[i].ItemNumber</td>
<td width="100"><img #Html.Raw(POSGuys.Controllers.Shims.Resize("/content/images/catalog/" + options[i].image, 200, 200, rescale: 2)) /></td>
<td>#Html.EditorFor(model => options[i].OptionName)</td>
<td>#Html.EditorFor(model => options[i].PGPrice)</td>
<td>#Html.EditorFor(model => options[i].OptionsMSRP)</td>
<td>#Html.EditorFor(model => options[i].Cost)</td>
<td>#Html.EditorFor(model => options[i].Description)</td>
<td>#Html.EditorFor(model => options[i].Rank)</td>
<td>#Html.EditorFor(model => options[i].Standard)</td>
<td><input type="submit" value="Save" class="btn btn-warning" /></td>
</tr>
}
}
Controller
[HttpPost]
public ActionResult Save(Option option)
{
var opt = StoreDatabase.Options.Find(option.OptionID);
if (opt != null)
{
StoreDatabase.Entry(opt).CurrentValues.SetValues(option);
StoreDatabase.SaveChanges();
}
return RedirectToAction("EditList", option.ProductID);
}
You just need to make sure that your code is generating the input field's with name values same as the property names of the Option class and model binding will work.
#for (int i = 0; i < options.Count(); i++)
{
using (Html.BeginForm("Save", "Option", FormMethod.Post))
{
#Html.EditorFor(model => options[i].OptionName,null,"OptionName")
#Html.EditorFor(model => options[i].PGPrice,null, "PGPrice")
<input type="submit" />
<br />
}
}
Now when you submit the form, the Default model binder will be able to map the form field values to the properties of the Option class object.
Much easier solution - create partial view.
If you have your main view as List your partial should be just Model and everything will work like it should.

How to get two different models on one page with Partial Views?

I have a UserManager page where an admin is able to get all the accounts that have currently been registered, delete accounts, update accounts, and register new accounts all from the same page.
I have a controller which reutrns all the users in db.users in a list.
public ActionResult UserManager()
{
if (User.IsInRole("1"))
{
var db = new ReviewLogsTestDBEntities();
return View(db.Users.ToList());
}
else
{
return RedirectToAction("Index", "Home");
}
}
That's working fine, and I have the delete working.
The problem comes when I want to get a registration form on the same page.
This what my view Looks like right now:
#model IEnumerable<ReviewLogs.User>
#{
ViewBag.Title = "UserManager";
}
<h2>UserManager</h2>
<div id="userDetails">
<table>
<tr>
<th>
#Html.DisplayNameFor(model => model.UserName);
</th>
<th>
#Html.DisplayNameFor(model => model.Role)
</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.UserName)
</td>
<td>
#Html.DisplayFor(modelItem => item.Role)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.ID }) |
#Html.ActionLink("Delete", "Del", new { id = item.ID}, new { onclick = "return confirm('Are you sure you wish to delete this article?')"});
</td>
</tr>
}
</table>
</div>
As you can see there is a model:
#model IEnumerable<ReviewLogs.User>
But I want my form to use its own RegisterModel which has required fields, regex statements, etc, that need to be filled. When the user presses submit they the UserManager page is reloaded and now shows the newly added user.
I thought I could create a partial view:
#model ReviewLogs.Models.RegisterModel
#{
ViewBag.Title = "Register";
ViewBag.SubHead = "Register";
}
#Html.ValidationSummary(true, "Creation of new Account has failed please check your fields.")
<p id="success">#ViewBag.SuccessMessage</p>
<br />
#using (Html.BeginForm())
{
//The Registration Page the user sees
//The userName label and textbox
<div class="inputFields">
#Html.LabelFor(model => model.userName)
#Html.TextBoxFor(model => model.userName)
#Html.ValidationMessageFor(model => model.userName)
</div>
//The accountType label and radioButton
<div class="radioButtonAccountType">
#Html.LabelFor(model => model.accountType)
#Html.RadioButtonFor(model => model.accountType, "1")<span class="adminLabel">Admin</span>
#Html.RadioButtonFor(model => model.accountType, "2")<span class="userLabel">User</span>
#Html.ValidationMessageFor(model => model.accountType)
</div>
<input class="submitButton" type="submit" value="Create User" style="margin-left:140px;margin-bottom: 20px;" />
}
And In my UserManager View I added:
#Html.Partial("Register");
But then I got this error:
The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[ReviewLogs.User]', but this dictionary requires a model item of type 'ReviewLogs.Models.RegisterModel'.
How can I get pass this so I am able to POST my registration?
Look for the #Html.Partial method overload which accepts a model, and call it via something like:
#Html.Partial("Register", Model.RegisterModel)
In order for this to work, probably the best bet would be to create a new Model that you pass into the UserManager view. This UserManagerModel will have two properties: IEnumerable<ReviewLogs.User>, used by the UserManagerView itself, and RegisterModel which you'll pass into the Partial view.
Just new up a copy of register model in your view and pass it to the partial:
#Html.Partial("Register", new Namespace.To.RegisterModel())
Just make sure to make the form for registering post to a separate action. You should not try to have this one action handle multiple different types of posts. You can pass a return URL (basically just Request.RawUrl) in a hidden field in your form, and then if this value is present, you can use it to redirect back to the same page. For example:
#Html.Hidden("returnUrl", Request.RawUrl)
Then,
[HttpPost]
public ActionResult Register(RegisterModel model, string returnUrl)
{
// do the register thing
if (!String.IsNullOrWhiteSpace(returnUrl))
{
return Redirect(returnUrl);
}
return RedirectToAction("Index"); // or whatever should be default
}

MVC5 Parent Child Single Edit Form [duplicate]

I am trying to return the results of a table back to the controller for further manipulation. Once returned to the controller the value shows as null. In the past I have been able to use #Html.HiddenFor to return the values but it doesn't seem to be working in this instance. Not sure what I am doing wrong here. Any help is greatly appreciated.
#model IEnumerable<Project.Models.Item>
#{
ViewBag.Title = "Welcome to The Project";
}
#using (Html.BeginForm("UpdateQuality", "Home", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
<div class="row">
<div class="form-group">
<table class="table table-bordered">
<tr>
<th>#Html.DisplayNameFor(m => m.Name)</th>
<th>#Html.DisplayNameFor(m => m.SellIn)</th>
<th>#Html.DisplayNameFor(m => m.Quality)</th>
</tr>
#for (int i = 0; i < Model.Count(); i++)
{
<tr>
<td>#Html.DisplayFor(m => m.ElementAt(i).Name)</td>
<td>#Html.DisplayFor(m => m.ElementAt(i).SellIn)</td>
<td>#Html.DisplayFor(m => m.ElementAt(i).Quality)</td>
#Html.HiddenFor(m => m.ElementAt(i).Name)
#Html.HiddenFor(m => m.ElementAt(i).SellIn)
#Html.HiddenFor(m => m.ElementAt(i).Quality)
</tr>
}
</table>
<div class="form-group">
<div style="margin-top: 50px">
<input type="submit" class="btn btn-primary" value="Advance Day"/>
</div>
</div>
</div>
</div>
}
And here is the controller which returns null.
public ActionResult UpdateQuality(List<Item> Items )
{
return View("Index", (object)Items);
}
You cannot use ElementAt() in a HtmlHelper method that generates form controls (look at the name attribute your generating - it does not match your model).
Either change the model to be IList<T>
#model List<Project.Models.Item>
and use a for loop
#for (int i = 0; i < Model.Count; i++)
{
....
#Html.HiddenFor(m => m.[i].Name)
....
or change use a custom EditorTemplate for typeof Item, and in the main view, use #Html.EditorFor(m => m) to generate the correct html for each item in the collection.

MVC 5 Viewmodel binding works but post back is partial filled

I have a parameterless Index for the HttpGet which works. But when I post it the HttpPost version of Index is invoked and the viewmodel object is passed in, but there is only the value of the dropdown in it. The rest is null (products, title)
[HttpPost]
public ActionResult Index(ProductsViewModel pvm)
{
// breakpoint on line 36, shows that pvm.Title is null and Products too.
return View(pvm);
}
My compilable and running example can be downloaded from my OneDrive http://1drv.ms/1zSsMkr
My view:
#model KleinKloteProductOverzicht.Models.ProductsViewModel
#using (Html.BeginForm("Index", "Products"))
{
<h2>#Html.DisplayFor(m => m.Title)</h2>
<input type="submit" value="post dit" /><br/>
<div class="row">
<div class="col-lg-2 col-md-2">
#Html.DropDownListFor(x => x.CurrentSort, EnumHelper.GetSelectList(typeof(SortOptions)), new { #class = "multiselect"})
</div>
</div>
if (Model.Products.Count() > 0)
{
<div class="row">
#foreach (var item in Model.Products)
{
#Html.DisplayFor(i => item.Name);
}
</div>
}
}
If I have this view model:
public class ViewModel
{
public string Name {get;set;}
public string SelectedLocation {get;set;}
public IEnumerable<SelectListItem> Locations {get;set;}
}
And your actions look like this:
public ActionResult MyForm()
{
var vm = new ViewModel
{
Locations = context.Locations.ToList() // Some database call
}
return View(vm);
}
[HttpPost]
public ActionResult MyForm(ViewModel vm)
{
vm.Locations // this is null
}
It is null because the model binder can't find a form control that is setting its data.
The <form> must set some data in the view for the model binder to pick it up.
<form>
Name: <input type="text" id="name" />
</form>
This will set the Name property on the view model, because the model bind can see the id of the form control and uses that to know what to bind to.
So in terms of your view, you need to make sure you wrap any content that you want to post back to the server with #using(Html.BeginForm())
Anyway this is my guess.
Well, you seem to be confused as to how [HttpPost] and form tags interact with eachother.
You see, when .NET MVC binds your parameters in your controller actions, it tries to derive that data from the request. For [HttpGet] it does this by looking at the query string.
For [HttpPost] calls, it also looks at the Request.Form. This variable is populated with the values of all input fields that were inside the form you submitted.
Now, this is your view:
#using (Html.BeginForm("Index", "Products"))
{
<h2>#Html.DisplayFor(m => m.Title)</h2>
<input type="submit" value="post dit" /><br/>
<div class="row">
<div class="col-lg-2 col-md-2">
#Html.DropDownListFor(x => x.CurrentSort, EnumHelper.GetSelectList(typeof(SortOptions)), new { #class = "multiselect" })
</div>
</div>
if (Model.Products.Count() > 0)
{
<div class="row">
#foreach (var item in Model.Products)
{
#Html.DisplayFor(i => item.Name);
}
</div>
}
}
You only have one select tag (generated by Dropdownlistfor) but no other inputs. That's why .NET MVC cannot infer any other data for your view model.
If you change your view to this:
#model KleinKloteProductOverzicht.Models.ProductsViewModel
#using (Html.BeginForm("Index", "Products"))
{
<h2>#Html.DisplayFor(m => m.Title)</h2>
<input type="submit" value="post dit" /><br/>
<div class="row">
<div class="col-lg-2 col-md-2">
#Html.DropDownListFor(x => x.CurrentSort, EnumHelper.GetSelectList(typeof(SortOptions)), new { #class = "multiselect" })
</div>
</div>
if (Model.Products.Count() > 0)
{
<div class="row">
#for (var i = 0; i < Model.Products.Count; i++)
{
#Html.DisplayFor(model => model.Products[i].Name)
#Html.HiddenFor(model => model.Products[i].ID)
}
</div>
}
}
You'll see I've added a hidden input (<input type="hidden">) for the product id. Note that the product name still will be null.
I would suggest you follow a tutorial on .NET MVC and read up on some of the concepts behind it, because the very fact that you ask this question reveals that you have much to learn.
Best of luck!
P.S. One last tip: #Html.Blablabla writes directly to your view. You usually don't need that ";" at the end, because it will be inside your generated html.
Your property is not associated with a "postable" control, therefore it will not be submitted along with the form data. If your really want to get the value in your Title property, just set it as a hidden input.
#Html.HiddenFor(m => m.Title)
A label will not be posted when submitting a form but an input will. This is exactly what HiddenFor does; it creates a hidden input element which will be picked up by the form submit.

inline models editor MVC3

Ola.. I have a problem with editing of few models in table. I have a List of models on page (with foreach), something like
<table id="grid-table" >
#foreach (var image in ViewBag.Images)
{
<tr>
<td >
<a href="#Url.Action("ShowFullImage", new { id = #image.ID })" rel="lightbox[roadtrip]" title="#image.Description" >
<img src="#Url.Action("ShowImageThumbneil", new { id = #image.ID })" alt="#image.AlternateText" />
</a>
</td>
<td >
#using (Html.BeginForm("SaveImageInfo", "Admin", FormMethod.Post))
{
#Html.TextAreaFor(m => m.Description) <br />
#Html.TextBoxFor(m => m.AlternateText) <br />
#Html.LabelFor(m => m.ID)
<div id="item-post" >
<input title="Подтвердить" type="submit" value="Подтвердить" />
</div>
}
</td>
</tr>
}
and I want to have a way to edit ONE model item. In controller I have something like this:
[HttpPost]
public ActionResult SaveImageInfo(ImageModel imageModel)
{
Image img = _core.GetImageByID(_client, imageModel.ID);
img.AlternateText = imageModel.AlternateText;
img.Description = imageModel.Description;
_core.SaveImageInfo(_client, img);
return View();
}
but, of course, it's not works..
Can somebody help me?
Change #Html.LabelFor(m => m.ID) with #Html.HiddenFor(m => m.ID). Contents of labels are not sent with POST, but contents of hidden fields are..
I am guessing a bit, as I don't know exactly what problem you are seeing, but if it is to do with it trying to redirect, following the save, then try changing from:
Html.BeginForm
to
Ajax.BeginForm

Categories