Passing the Model values back to Controller Method after submission - asp.net-mvc

I have a view that asks a user to upload Excel file, my controller reads the excel file and returns the results as a Model. The Model contains List of Strings called AssingednUmbers public List<String> AssignedNumbers { get; set; }
The values are displayed on a View Page. Everything works fine upto this step. Now I have a Method called DownloadFile
[HttpPost]
public FileResult DownloadFile(OnvoyModel model)
It has a Model entry and returns csv file . The conversion and everything is working fine. but The Model that I am passing to DownloadFile is always Empty. after uploading Excel file and returning the results(Numbers) on the page when they user click on the Download Button, the controller calles the DownloadFile Method but the Model is Null. How can I pass the Model with the List inside of it from before back to my Controller
// Looping through my List that comes from the Model
<table>
#foreach (var Number in Model.AssignedNumbers) {
<li>
#Number
</li>
#Html.HiddenFor(x => x.AssignedNumbers);
}
</table>
<td>
///Submitting to download the new list and calling the Method
#using (Html.BeginForm("DownloadFile", "PhoneBookProcessing", FormMethod.Post))
{
#Html.Action("DownloadFile", new { model = Model.AssignedNumbers })
}
<div class="form-group">
<button class="btn btn-default" type="submit">Download This File</button>
</div>
}
Model
public class OnvoyModel
{
public List<String> AssignedNumbers { get; set; }
}

Just found out .
Pass it as a hidden field
View
for (int item = 0; item < subjectsCount; item++)
{
#Model.AssignedNumbers[item].ToString()
#Html.HiddenFor(x => x.AssignedNumbers[item])
}
}

Related

how to make selectable items using viewbag and input type checkbox, and access checked items in controllers [duplicate]

This question already has answers here:
How does MVC 4 List Model Binding work?
(5 answers)
Closed 7 years ago.
I have a list of items that will be associated to a user. It's a one-to-many relationship. I want the entire list of items passed into the view so that they can choose from ones that are not associated to them yet (and also see those that are already associated). I want to create checkboxes from these. I then want to send the selected ones back into the controller to be associated. How can I pass in the list of all of them, including those that aren't yet associated, and reliably pass them back in to be associated?
Here's what I tried first, but it's clear this won't work as I'm basing the inputs off the items passed in via the AllItems collection, which has no connection to the Items on the user itself.
<div id="item-list">
#foreach (var item in Model.AllItems)
{
<div class="ui field">
<div class="ui toggle checkbox">
<input type="checkbox" id="item-#item.ItemID" name="Items" value="#item.Active" />
<label for="item-#item.ItemID">#item.ItemName</label>
</div>
</div>
}
</div>
You cannot bind to a collection using a foreach loop. Nor should you be manually generating your html, which in this case would not work because unchecked checkboxes do not post back. Always use the strongly typed html helpers so you get correct 2-way model binding.
You have not indicated what you models are, but assuming you have a User and want to select Roles for that user, then create view models to represent what you want to display in the view
public class RoleVM
{
public int ID { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
}
public class UserVM
{
public UserVM()
{
Roles = new List<RoleVM>();
}
public int ID { get; set; }
public string Name { get; set; }
public List<RoleVM> Roles { get; set; }
}
In the GET method
public ActionResult Edit(int ID)
{
UserVM model = new UserVM();
// Get you User based on the ID and map properties to the view model
// including populating the Roles and setting their IsSelect property
// based on existing roles
return View(model);
}
View
#model UserVM
#using(Html.BeginForm())
{
#Html.HiddenFor(m => m.ID)
#Html.DisplayFor(m => m.Name)
for(int i = 0; i < Model.Roles.Count; i++)
{
#Html.HiddenFor(m => m.Roles[i].ID)
#Html.CheckBoxFor(m => m.Roles[i].IsSelected)
#Html.LabelFor(m => m.Roles[i].IsSelected, Model.Roles[i].Name)
}
<input type"submit" />
}
Then in the post method, your model will be bound and you can check which roles have been selected
[HttpPost]
public ActionResult Edit(UserVM model)
{
// Loop through model.Roles and check the IsSelected property
}
It doesn't look like you're going to be deleting the checkboxes dynamically so that makes this problem a lot easier to solve. NOTE: The following solution won't work as expected if you allow clients or scripts to dynamically remove the checkboxes from the page because the indexes will no longer be sequential.
MVC model binding isn't foolproof so sometimes you have to help it along. The model binder knows it needs to bind to a property called Items because the input field's name is Items, but it doesn't know Items is a list. So assuming in your controller you have a list of items to model bind to called Items what you need to do is help MVC recognize that you're binding to a list. To do this specify the name of the list and an index.
<div id="item-list">
#for (var i = 0; i < Model.AllItems.Count; i++)
{
<div class="ui field">
<div class="ui toggle checkbox">
<input type="checkbox" id="item-#Model.AllItems[i].ItemID" name="Items[#i]" value="#Model.AllItems[i].Active" />
<label for="item-#Model.AllItems[i].ItemID">#Model.AllItems[i].ItemName</label>
</div>
</div>
}
</div>
The key line here is this:
<input type="checkbox" id="item-#Model.AllItems[i].ItemID" name="Items[#i]" value="#Model.AllItems[i].Active" />
Notice the Items[#i]? That's telling the model binder to look for a property named Items and bind this value to the index at i for Items.

Model Not Submitting / Binding Correctly on POST

Scenario:
I have a table with rows that are populated from a ViewModel. There are checkboxes for each row that allow the user to check 1 or more of the rows and then choose from actions in a dropdown menu to make edits to properties on the selected rows.
Everything works fine to this point, and I can get the ViewModel to pass correctly and then use it and all it's properties in a POST Action method. I could make the changes based on the option the user picked.
However, since some of the options in the dropdown would make fairly substantial and irreversible changes, I am calling a new View with a GET and populating a new table with just the selected rows, and asking the user to confirm they want to make the changes. Everything is still good up to this point. The new View populates as expected with only the rows that were selected in the previous View.
Problem:
After the user confirms their intent, an Action method is called with POST. The ViewModel that correctly populated the current View is making its way into the controller correctly. I get the ViewModel, but not with the same properties as the one that populated the View.
ViewModel
public class ProjectIndexViewModel
{
public List<ProjectDetailsViewModel> Projects { get; set; }
public string FlagFormEditProjects { get; set; }
public string FlagFormNewProjectStatus { get; set; }
}
The List<ProjectDetailsViewModel> Projects is what is used to populate the rows in the table, and Projects are what are not binding correctly in the POST Action methods in the controller.
Initial View where the checkboxes are selected. Note the example of one of the javascript functions that is called when one of the dropdown options is selected, which is what submits the form.
#using (Html.BeginForm("EditProjectsTable", "Project", FormMethod.Get, new { name = "formEditProjects", id = "formEditProjects" }))
{
#Html.HiddenFor(item => item.FlagFormEditProjects)
#Html.HiddenFor(item => item.FlagFormNewProjectStatus)
....
<table>
<thead>
....
</thead>
<tbody>
#for (int i = 0; i < Model.Projects.Count; i++)
{
<tr>
<td>#Html.DisplayFor(x => x.Projects[i].ProjectNumber)</td>
<td>#Html.DisplayFor(x => x.Projects[i].ProjectWorkType)</td>
.... // more display properties
<td>
#Html.CheckBoxFor(x => x.Projects[i].Selected, new { #class = "big-checkbox" })
#Html.HiddenFor(x => x.Projects[i].ProjectModelId)
</td>
</tr>
}
</tbody>
</table>
}
function submitFormRemoveProjects() {
$("#FlagFormEditProjects").attr({
"value": "RemoveProjects"
});
$('#formEditProjects').submit();
}
Action method that returns the "confirmation" View (works fine)
[HttpGet]
[Authorize(Roles = "Sys Admin, Account Admin, User")]
public async Task<ActionResult> EditProjectsTable([Bind(Include = "Projects,FlagFormEditProjects,FlagformNewProjectStatus")]ProjectIndexViewModel projectIndexViewModel)
{
// Repopulate the Projects collection of ProjectIndexViewModel to
// include only those that have been selected
return View(projectIndexViewModel);
}
View that is returned from Action method above (works fine) Note that the Action method that gets called is set dynamically with the actionName variable in the Html.BeginForm call.
#using (Html.BeginForm(actionName, "Project", FormMethod.Post))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(model => model.FlagFormNewProjectStatus)
....
<table>
<thead>
....
</thead>
<tbody>
#for (int i = 0; i < Model.Projects.Count; i++)
{
<tr>
<td>#Html.HiddenFor(x => x.Projects[i].ProjectModelId)</td>
<td>#Html.DisplayFor(x => x.Projects[i].ProjectNumber)</td>
<td>#Html.DisplayFor(x => x.Projects[i].ProjectWorkType)</td>
.... // more display properties
</tr>
}
</tbody>
</table>
<input type="submit" value="Delete Permanently" />
}
An example of one of the Controller Action methods that is called from this View, and that does not have the same Project that was in the View. Somehow, it has the same number of Projects that were originally selected, but if only one was selected, it has the Project with the lowest Model Id. I'm not sure how else to describe what's happening. But in summary, the correct ViewModel is not making it's way into the POST method example shown below.
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize(Roles = "Sys Admin, Account Admin")]
public async Task<ActionResult> DeleteConfirmedMultipleProjects([Bind(Include = "Projects")] ProjectIndexViewModel projectIndexViewModel)
{
if (ModelState.IsValid)
{
// Remove Projects from db and save changes
return RedirectToAction("../Project/Index");
}
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Please help!
The issue is that when you submit to the EditProjectsTable() method from the first view, the values of all form controls are added to ModelState.
Repopulating your collection of ProjectDetailsViewModel does not update ModelState, and when you return the view, the DisplayFor() methods will display the correct values because DisplayFor() uses the values of the model, however your
#Html.HiddenFor(x => x.Projects[i].ProjectModelId)
will use the values from ModelState, as do all the HtmlHelper methods that generate form controls (except PasswordFor()).
One way to solve this is to call ModelState.Clear() before you return the view in the EditProjectsTable() method. The HiddenFor() method will now use the value of the model because there is no ModelState value.
[HttpGet]
[Authorize(Roles = "Sys Admin, Account Admin, User")]
public async Task<ActionResult> EditProjectsTable(ProjectIndexViewModel projectIndexViewModel)
{
// Repopulate the Projects collection of ProjectIndexViewModel to
// include only those that have been selected
ModelState.Clear(); // add this
return View(projectIndexViewModel);
}
For a explanation of why this is the default behavior, refer the second part of this answer.
Side note: Your using a view model, so there is no point including a [Bind] attribute in your methods.
I think your problems comed from this part:
#Html.CheckBoxFor(x => x.Projects[i].Selected, new { #class = "big-checkbox" })
#Html.HiddenFor(x => x.Projects[i].ProjectModelId)
I had this error before and what I did is adding a Boolean property to ProjectDetailsViewModel like IsSelected.
then you should have :
#Html.CheckBoxFor(x => x.Projects[i].IsSelected, new { #class = "big-checkbox" })
Then on method you should add:
foreach (var project in ProjectIndexViewModel.Projects )
{
if (project.IsSelected==true)
"put your logic here"
}

How can i maintain objects between postbacks?

I'm not so experienced using MVC. I'm dealing with this situation. Everything works well until call the HttpPost method where has all its members null. I don't know why is not persisting all the data on it.
And everything works well, because I can see the data in my Html page, only when the user submit the information is when happens this.
[HttpGet]
public ActionResult DoTest()
{
Worksheet w = new Worksheet(..);
return View(w);
}
[HttpPost]
public ActionResult DoTest(Worksheet worksheet)
{
return PartialView("_Problems", worksheet);
}
This is class which I'm using.
public class Worksheet
{
public Worksheet() { }
public Worksheet(string title, List<Problem> problems)
{
this.Title = title;
this.Problems = problems;
}
public Worksheet(IEnumerable<Problem> problems, WorksheetMetadata metadata, ProblemRepositoryHistory history)
{
this.Metadata = metadata;
this.Problems = problems.ToList();
this.History = history;
}
public string Title { get; set; }
public List<Problem> Problems { get; set; } // Problem is an abstract class
public WorksheetMetadata Metadata { get; set; }
public ProblemRepositoryHistory History { get; set; }
}
And my razor view.... the razor view shows successfully my view. I realized something rare, please note in my 5 and 6 lines that I have HiddenFor method, well if I used that, when calls HTTPPOST persists the data, I don't know why.
#model Contoso.ExercisesLibrary.Core.Worksheet
<div id="problemList">
<h2>#Html.DisplayFor(model => model.Metadata.ExerciseName)</h2>
#Html.HiddenFor(model => model.Metadata.ExerciseName)
#Html.HiddenFor(model => model.Metadata.ObjectiveFullName)
#for (int i = 0; i < Model.Problems.Count; i++)
{
<div>
#Html.Partial(Contoso.ExercisesLibrary.ExerciseMap.GetProblemView(Model.Problems[i]), Model.Problems[i])
</div>
}
</div>
UPDATE
I'm using a static class to get the view name, but as I'm testing I'm just using this Partial view
#model Contoso.ExercisesLibrary.AbsoluteArithmetic.Problem1
<div>
<span style="padding:3px; font-size:18px;">#Model.Number1</span>
<span style="padding:5px; font-size:18px;">+</span>
<span style="padding:5px; font-size:18px;">#Model.Number2</span>
<span style="padding:5px; font-size:18px;">=</span>
<span style="font-size:18px">
#Html.EditorFor(model => model.Result, new { style = "width:60px; font-size:18px;" })
#Html.ValidationMessageFor(model => model.Result)
</span>
</div>
#section Scripts {
}
And here the user do the post
#model Contoso.ExercisesLibrary.Core.Worksheet
<form method="post">
#Html.Partial("_Problems", Model)
<input type="submit" value="Continue" />
</form>
The Model Binder will 'bind' or link input fields on your view to the model. It will not bind display fields (like label), that is why you need the HiddenFor it will add an <input type="hidden" which will then be bound to the Model when you Post.
You can use 'TempData'. It is used to pass data from current request to subsequent request means incase of redirection.
This link also helps you.
TempData
SO Tempdata
Make sure your form tag looks like the following, for instance the controller name, action method, the form method and an id for the form. I am referring to the #using statement. In my case the controller name is RunLogEntry, the action method is Create and the id is form.
Normal Post from View to Controller
#using (Html.BeginForm("Create", "RunLogEntry", FormMethod.Post, new { id = "form", enctype = "multipart/form-data" }))
{
<div id="main">
#Html.Partial("_RunLogEntryPartialView", Model)
</div>
}
If you want to post via Jquery, could do the following:
$.post("/RunLogEntry/LogFileConfirmation",
$("#form").serialize(),
function (data) {
//this is the success event
//do anything here you like
}, "html");
You must specify a form with correct attribute in your view to perform post action
<form action="Test/DoTest" method="post">
...
</form>
or
#using(Html.BeginForm("DoTest", "Test", FormMethod.Post)) {
...
}
The second is recommended.
Put your entire HTML code under:
#using(Html.BeginForm())
tag.

ASP.NET MVC, passing Model from View to Controller

I'm having trouble with ASP.NET MVC and passing data from View to Controller. I have a model like this:
public class InputModel {
public List<Process> axProc { get; set; }
public string ToJson() {
return new JavaScriptSerializer().Serialize(this);
}
}
public class Process {
public string name { get; set; }
public string value { get; set; }
}
I create this InputModel in my Controller and pass it to the View:
public ActionResult Input() {
if (Session["InputModel"] == null)
Session["InputModel"] = loadInputModel();
return View(Session["InputModel"]);
}
In my Input.cshtml file I then have some code to generate the input form:
#model PROJ.Models.InputModel
#using(Html.BeginForm()) {
foreach(PROJ.Models.Process p in Model.axProc){
<input type="text" />
#* #Html.TextBoxFor(?? => p.value) *#
}
<input type="submit" value="SEND" />
}
Now when I click on the submit button, I want to work with the data that was put into the textfields.
QUESTION 1: I have seen this #Html.TextBoxFor(), but I don't really get this "stuff => otherstuff". I concluded that the "otherstuff" should be the field where I want to have my data written to, in this case it would probably be "p.value". But what is the "stuff" thing in front of the arrow?
Back in the Controller I then have a function for the POST with some debug:
[HttpPost]
public ActionResult Input(InputModel m) {
DEBUG(m.ToJson());
DEBUG("COUNT: " + m.axProc.Count);
return View(m);
}
Here the Debug only shows something like:
{"axProc":[]}
COUNT: 0
So the returned Model I get is empty.
QUESTION 2: Am I doing something fundamentally wrong with this #using(Html.BeginForm())? Is this not the correct choice here? If so, how do I get my model filled with data back to the controller?
(I cannot use "#model List< Process >" here (because the example above is abbreviated, in the actual code there would be more stuff).)
I hope someone can fill me in with some of the details I'm overlooking.
Change your view to some thing like this to properly bind the list on form submission.
#using(Html.BeginForm()) {
for(int i=0;i<Model.axProc.Count;i++){
<span>
#Html.TextBoxFor(model => model.axProc[i].value)
</span>
}
<input type="submit" value="SEND" />
}
In #Html.TextBoxFor(stuff => otherstuff) stuff is your View's model, otherstuff is your model's public member.
Since in the View you want to render input elements for the model member of a collection type (List), you should first create a separate partial view for rendering a single item of that collection (Process). It would look something like this (name it Process.cshtml, for example, and place into the /Views/Shared folder):
#model List<PROJ.Models.Process>
#Html.TextBoxFor(model => p.value)
Then, your main View would look like this:
#model PROJ.Models.InputModel
#using(Html.BeginForm()) {
foreach(PROJ.Models.Process p in Model.axProc){
#Html.Partial("Process", p)
}
<input type="submit" value="SEND" />
}
Also, check that the loadInputModel() method actually returns something, e.g. not an empty list.

How to interact with List<t> in MVC

I have the following code in my view
<% foreach (var item in Model.stats)
{%>
<label style="style="float:left;"><%= item.Stat_Caption %></label>
<%=Html.TextBox(item.Stat_Caption,item.Stat_Value) %>
<%} %>
I'm trying to turn my stats object which is just a list collection, into a list of textboxes so the user can update them.
which i have got working, how do i once the user updates the textboxes apply the values back to the list collection?
You need to wrap the textboxes in a form:
<% using (Html.BeginForm()) { %>
<% foreach (var item in Model.stats)
{%>
<label style="style="float:left;"><%= item.Stat_Caption %></label>
<%=Html.TextBox(item.Stat_Caption,item.Stat_Value) %>
<%} %>
<input type="submit" value="Save" class="button" /></td>
<% } %>
When you press the submit button, it will do a standard POST with key/value pairs like so:
Box1 : Hello
Box2 : World
On the controller side, you need to have a method that receives the POST request:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Entity entity)
{
// code goes here
}
where Entity is your data model object. The default MVC model binder uses reflection to populate your entity's fields, so if the entity was like this:
public class Entity()
{
public string Box1 { get; set; }
public string Box2 { get; set; }
}
Then Box1 and Box2 will be set to the values that were sent in the POST request.
If you don't have an entity, then you can use this:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(FormCollection collection)
{
// code goes here
}
where collection is a dictionary of objects. The bad thing with using this dictionary is that it's a dictionary of Object types, so you'll have to grab the data and cast it back to whatever type it's supposed to be.

Resources