I have a viewmodel that contains a List, such as this:
class School
{
public List<Student> Students { get; set; }
}
class Student
{
public int Id { get; set; }
public String Name { get; set; }
}
I have a form on which I am submitting multiple students info related to single school. Now I can add/remove a single student from the form.
Adding works fine, but my issue and my question is related to deletion of a student.
So let me explain this with an example:
Lets say I add 3 students then there names and Ids will be binded to the model in this way:
Students[0].Id = "1"
Students[0].Name = "Student A"
Students[1].Id = "2"
Students[1].Name = "Student B"
Students[2].Id = "3"
Students[2].Name = "Student C"
If I save this it works just fine. But lets say I delete the student with
Id ="2".On submitting what is happening is that only the student with id = "1"
is getting binded and rest after deleted index(that is student with id="3") is not getting binded.
My question is that : Is it at all possible to bind that id="3" after deletion of id="2" ?
Or In proper terms is it possible to bind/submit a list with skipped indices.
I found below mentioned articles on stackoverflow itself but what I can infer from them is bit contradicting or maybe I am not understanding them properly.
Skipping Not possible
Skipping possible
I am not good with explaining problems. So please tell me if I can add anything to make it more descriptive.
Thank you.
Example Delete Code:
Fiddle for delete Js code
I am posting this answer for future reference for anyone else who is having same doubts.
So if you have a collection like this:
public class Book {
public string Title { get; set; }
public string Author { get; set; }
public DateTime DatePublished { get; set; }
}
//Action method on HomeController
public ActionResult UpdateProducts(ICollection<Book> books) {
return View(books);
}
And your form after adding 3 books looks like this:
<form method="post" action="/Home/Create">
<input type="text" name="[0].Title" value="Curious George" />
<input type="text" name="[0].Author" value="H.A. Rey" />
<input type="text" name="[0].DatePublished" value="2/23/1973" />
<input type="text" name="[1].Title" value="Code Complete" />
<input type="text" name="[1].Author" value="Steve McConnell" />
<input type="text" name="[1].DatePublished" value="6/9/2004" />
<input type="text" name="[2].Title" value="The Two Towers" />
<input type="text" name="[2].Author" value="JRR Tolkien" />
<input type="text" name="[2].DatePublished" value="6/1/2005" />
<input type="submit" />
</form>
Now If you guys want to add delete functionality such that your form might contain non-sequential entries, then something like this can be done:
<form method="post" action="/Home/Create">
<input type="hidden" name="products.Index" value="cold" />
<input type="text" name="products[cold].Name" value="Beer" />
<input type="text" name="products[cold].Price" value="7.32" />
<input type="hidden" name="products.Index" value="123" />
<input type="text" name="products[123].Name" value="Chips" />
<input type="text" name="products[123].Price" value="2.23" />
<input type="hidden" name="products.Index" value="caliente" />
<input type="text" name="products[caliente].Name" value="Salsa" />
<input type="text" name="products[caliente].Price" value="1.23" />
<input type="submit" />
</form>
In the example above, we provide a hidden input with the .Index suffix for each item we need to bind to the list. This will give the model binder a nice collection of indices to look for when binding to the list.
Related
So I have complex object that should be displayed on the page. Some properties of this object should be disabled (or readonly) for users in certain access roles. However, I noticed that if I edit this <input disabled .../> in devtools and remove disabled property and add arbitrary text, then model binder will happily take this value and put it in form model when submitted via OnPost action.
Here is example code:
<form method="post">
<div class="form-group col-sm-6">
<label asp-for="FormModel.FirstName" class="control-label">First Name</label>
<input asp-for="FormModel.FirstName"
disabled="disabled"
class="form-control"
/>
</div>
<div class="form-group col-sm-6">
<label asp-for="FormModel.LastName" class="control-label">Last Name</label>
<input asp-for="FormModel.LastName"
class="form-control"
/>
</div>
<input type="submit" value="Save" class="btn btn-primary" />
</form>
And the page model:
public class IndexModel : PageModel
{
private readonly MyDbMock _myDb;
[BindProperty]
public FormModel FormModel { get; set; }
public IndexModel(MyDbMock myDb)
{
_myDb = myDb;
}
public void OnGet()
{
FormModel = new FormModel
{
FirstName = _myDb.FormModel.FirstName,
LastName = _myDb.FormModel.LastName
};
}
public IActionResult OnPost()
{
_myDb.FormModel = FormModel;
return RedirectToAction(nameof(OnGet));
}
}
public class FormModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class MyDbMock
{
public FormModel FormModel { get; set; } = new FormModel
{
FirstName = "John",
LastName = "Smith"
};
}
When I open this page for the first time I get John in the disabled input with First name label. This is fine. Then when I click Save my OnPost method will get FormModel object where FirstName is null. This is reflected in the updated page. Can this be prevented and tell model binder just to not touch this property? Ie. leave it as John when form is submitted and not set it to null because of it being disabled?
The other issue I have is that an advanced user can remove disabled attribute from the First Name input and just submit arbitrary value. Can this also be prevented by telling model binder same thing as above? Eg. leave First Name field as-is when it was sent in GET request, no updates.
Here is the demo of all this:
I know I can handle this in backend code by preventing updating of the disabled fields. However I am looking some more elegant and declarative way if it exists. In my main project the form model is much complex and has objects within objects so ideally I'd like to skip adding additional logic to action handler for this.
Try to add a hidden input to bind FormModel.FirstName,and change the name of the disabled input,so that the value of disabled input will not be binded to FormModel.FirstName.
<form method="post" >
<div class="form-group col-sm-6">
<label asp-for="FormModel.FirstName" class="control-label">First Name</label>
<input asp-for="FormModel.FirstName" name="FirstName"
disabled="disabled"
class="form-control" />
<input asp-for="FormModel.FirstName" hidden/>
</div>
<div class="form-group col-sm-6">
<label asp-for="FormModel.LastName" class="control-label">Last Name</label>
<input asp-for="FormModel.LastName"
class="form-control" />
</div>
<input type="submit" value="Save" class="btn btn-primary" />
</form>
result:
This is what I'm trying to do:
I have a form with some input fields. Part of the form allows user to choose multiple options (Books) but will not know how many.
Model:
public class Data
{
public string name { get; set; }
public string age { get; set; }
...
public List<Books> books { get; set; }
...
}
And,
public class Books
{
public string Title { get; set; }
public string Author { get; set; }
}
View:
#model Applicants.Models.Data
...
<input type="text" name="Title" value="" />
<input type="text" name="Author" value="" />
My question is, how do I submit multiple titles and authors along with other form data? And how to properly name the input fields?
I have read this https://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/
But the example only submitted a List, not with other data.
Thanks.
Since you're using Razor view, you can use strongly-typed #Html.TextBoxFor() helpers and use for loop to generate multiple textboxes inside books list:
#model Applicants.Models.Data
#using (Html.BeginForm())
{
#Html.TextBoxFor(model => model.name)
#Html.TextBoxFor(model => model.age)
#for (int i = 0; i < Model.books.Count; i++)
{
#Html.TextBoxFor(model => model.books[i].Title)
#Html.TextBoxFor(model => model.books[i].Author)
}
#* don't forget to add submit button here *#
}
The loop will produce <input> elements like this example below, assumed that you have POST action which has Applicants.Models.Data as viewmodel parameter:
<input name="books[0].Title" type="text" value="sometitle">
<input name="books[0].Author" type="text" value="somevalue">
<input name="books[1].Title" type="text" value="sometitle">
<input name="books[1].Author" type="text" value="somevalue">
<!-- other input elements depending on books list count -->
Refer to this fiddle for working example.
you can do like this for bind model for normal html syntax its just example
you need modified according to your need
<input type="text" name="Model.name" value="Curious George" />
<input type="text" name="Model.age" value="H.A. Rey" />
<input type="text" name="Model.books[0].Title" value="Curious George" />
<input type="text" name="Model.books[0].Author" value="H.A. Rey" />
My controller I cant seem to bind the submitdata method to check box with id=answerA. At the moment I just want to insert one value the later will adjust accommodate 3 answer options:
[HttpPost]
public ActionResult SubmitData(QuestionnareModels models)
{
using (QUESTIONNAREDataContext db = new QUESTIONNAREDataContext())
{
SubmitTable answerSubmit = new SubmitTable()
{
SubmitID = 1,
CorrectAnswer = models.AnswerA
};
db.SubmitTables.InsertOnSubmit(answerSubmit);
db.SubmitChanges();
}
return RedirectToAction("Index");
}`
My view, where I render my check boxes from a linq database:
<div class="container">
<h2><mark>Questionnare system</mark></h2>
#using (Html.BeginForm("uploadAnswer"))
{
foreach (var questionType in Model)
{
#questionType.Question
<br/>
<br/>
<input type="checkBox" id="AnswerA" />
#questionType.AnswerA
<br />
<input type="checkBox" id="AnswerB" />
#questionType.AnswerB
<br />
<input type="checkBox" id="AnswerC" />
#questionType.Answerc
<br/>
<br/>
}
<br />
<button type="submit" class="btn btn-success" id="submitDataButton">Submit</button>
}
</div>
Your code has multiple problems including invalid html (duplicate id attributes), missing name attributes (nothing in the form will post back), incorrect use of checkboxes (unchecked checkboxes do not post back so there is no way of knowing which answer belongs with which question), posting back to the wrong method and allowing multiple answers for the same question. You need to create view models that represent what you want to display and edit
View models
public class QuestionVM
{
public int ID { get; set; }
public string Text { get; set; }
public int SelectedAnswer { get; set; }
public List<AnswerVM> AnswerList { get; set; }
}
public class AnswerVM
{
public int ID { get; set; }
public string Text { get; set; }
}
Controller
public ActionResult Create()
{
List<QuestionVM> model = new QuestionVM();
// populate your questions and the possible answers for each question
return View(model);
}
[HttpPost]
public ActionResult Create(List<QuestionVM> model)
{
// The model now contains the ID of each question and the ID of its selected answer
}
View
#model List<QuestionVM>
#using(Html.BeginForm())
{
for(int i = 0; i < Model.Count; i++)
{
#Html.HiddenFor(m => m[i].ID)
#Html.DisplayFor(m => m[i].Text)
foreach(AnswerVM answer in Model[i].AnswerList)
{
string id = "answer" + answer.ID;
#Html.RadioButtonFor(m => m[i].SelectedAnswer, answer.ID, new { id = #id })
<label for="#id">#answer.Text</label>
}
}
<input type="submit" value="Save" />
}
Looking at you controller code, I suspect your database structure is wrong. You would need a table for Questions (ID, Text, etc), a table for Answers (ID, Text, QuestionID, etc) and a table for users answers to questions (say) UserQuestionaires (UserID, QuestionID, AnswerID, etc)
<div class="container">
<h2><mark>Questionnare system</mark></h2>
#using (Html.BeginForm("SubmitData"))
{
foreach (var questionType in Model)
{
#questionType.Question
<br/>
<br/>
<input type="checkBox" name="AnswerA" />
#questionType.AnswerA
<br />
<input type="checkBox" name="AnswerB" />
#questionType.AnswerB
<br />
<input type="checkBox" name="AnswerC" />
#questionType.Answerc
<br/>
<br/>
<button type="submit" class="btn btn-success" id="submitDataButton">Submit</button>
}
<br />
}
</div>
Two issue
BeginForm should have method or action you have specificed in Controller
Submit button must be inside form.
You should not use ID. You have to use name.
If i understand your problem, I can see the 'form' action in html is "uploadAnswer" where as you posted the action method code for "SubmitData".
So what happens if you change "uploadAnswer" in Html.BeginForm("uploadAnswer") to Html.BeginForm("SubmitData"), are you able to hit the controller ?
Also for proper model binding, the name of html element and the property name of model should be same. So change html to: <input type="checkBox" name="AnswerA" id="AnswerA" /> (Assuming "AnswerA" is a property of your Questionaire model.
If you want to model bind to a collection, please take a look at here and here.
I have a model with a list property. In my view the user will fill in a fixed number of similar data (name number etc). How can I collect that information and create a list of it on the post? Thanks for any help.
public class mainonject
{
public List<test> list{ get; set; }
}
public class test
{
string name;
string number;
}
<form>
HOW DO I COLLECT THE PROPERTY list AS A LIST TO THE SERVER
<input type="submit" value="submit"/>
</form>
Just add the fields to your form and use the right indexes in there name properties:
<input name="list[0].Name" type="text" />
<input name="list[0].Number" type="text" />
<input name="list[1].Name" type="text" />
<input name="list[1].Number" type="text" />
...
I have a MVC2-application. in this application I have a strongtyped view contending the modell NewHorseModel:
public class NewHorseModel
{
public List<Category> Faehigkeit { get; set; }
}
public class Choice
{
public int Id { get; set; }
public string Name { get; set; }
public string Beschreibung { get; set; }
public bool Selected { get; set; }
}
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public string Beschreibung { get; set; }
public List<Category> Subcategories { get; set; }
public List<Choice> Choices { get; set; }
public int Parent { get; set; }
}
The View looks like this:
<p>
<input faehigkeit_ID="1" id="Selected" name="Selected" type="checkbox" value="true" />
<input name="Selected" type="hidden" value="false" />
Mitteltrab
<input id="Id" name="Id" type="hidden" value="1" />
</p>
<p>
<input faehigkeit_ID="2" id="Selected" name="Selected" type="checkbox" value="true" />
<input name="Selected" type="hidden" value="false" />
Arbeitstrab
<input id="Id" name="Id" type="hidden" value="2" />
</p>
<p>
<input faehigkeit_ID="3" id="Selected" name="Selected" type="checkbox" value="true" />
<input name="Selected" type="hidden" value="false" />
Trab versammelt
<input id="Id" name="Id" type="hidden" value="3" />
</p>
<p>
<input faehigkeit_ID="11" id="Selected" name="Selected" type="checkbox" value="true" />
<input name="Selected" type="hidden" value="false" />
Trab
<input id="Id" name="Id" type="hidden" value="11" />
</p>
The view is created like in this post:
Categories and Subcategories MVC2
now I want to do a post, but how to get the datas?
When I do a post like this:
[HttpPost]
public void myAction(NewHorseModel newHorseModel)
{
// ...
}
Faehigkeit in NewHorseModel is null
Here my ASPX Code:
<div id="Div2">
<%
foreach (Category item2 in Model.Faehigkeit)
{
Html.RenderPartial("Faehigkeit", item2);
}
%>
</div>
The partial view Category (strong typed model Category):
<%
if (Model.Choices != null)
{
foreach (var item in Model.Choices)
{
Html.RenderPartial("Choice", item);
}
}
if (Model.Subcategories != null)
{
foreach (var item in Model.Subcategories)
{
Html.RenderPartial("Faehigkeit", item);
}
}
%>
And the partialview Choices (strongtyped model choice)
<p>
<%: Html.CheckBoxFor(m => m.Selected, new { faehigkeit_ID = Model.Id }) %>
<%: Model.Name %>
<%: Html.HiddenFor(u=>u.Id) %>
</p>
Update
Next test:
in the Faehigkeit.ascx partial I have added this code:
<input type="hidden" name="Faehigkeit[<%=Model.Id%>].Id" value="<%=Model.Id%>" />
<input type="hidden" name="Faehigkeit[<%=Model.Id%>].Name" value="<%: Model.Name%>" />
in the Choices.ascx partial I have added following code:
<input type="checkbox" name="Faehigkeit[0].Choices[<%=Model.Id%>].Selected" />
I don't need to know which choice is in wich category. I kust need to know which choice ID is checked and which on not.
The HTML-Output looks like this:
<input type="hidden" name="Faehigkeit[1].Id" value="1" />
<input type="hidden" name="Faehigkeit[1].Name" value="Qualität der Gangarten" />
<input type="hidden" name="Faehigkeit[1].Choices[4].Id" value="4" />
<input type="checkbox" name="Faehigkeit[1].Choices[4].Selected" />
My controler looks like this:
[HttpPost]
public ActionResult CreateNewHorse(NewHorseModel collection)
{
if (User.Identity.IsAuthenticated)
{
return View();
}
else
{
return View("Account/LogOn");
}
}
If I try to get the value of "Faehigkeit" -> "Choices" Every thing is Null (the Name of the "Faehigkeit", the ID of "Faehigkeit", and there are no choices
An image that shows the content of the NewHorseModel during debuging:
http://i.stack.imgur.com/6yLZW.png
Thank you very much!
There are 2 blog posts that I've written and will address your problem:
How to post IList<T> to controller actions is explained here and will guide you from start to finish so you'll understand how it actually works and why.
And since you may have have complex JSON objects on the client side and would like them to be model bound on the server in your action method (as is in your case), this post may be as well an interesting read. It explains the problem of sending complex JSON objects and provides a simple jQuery plugin that converts client objects into a form that will easily be data bound to your strong type action method parameters.
Note: There's also the possibility of using JsonValueProviderFactory as explained by Phil Haack, but that solution requires changes on the client as well as on the server side (because you're using MVC2).
Based on your code
You've only provided some parts of your code (and some that aren't really relevant but anyway) and I'm going to make an observation for your case.
All input fields that you wish to model bind on the server need to have correct names. From the rendered checkbox names we can see that is not the case at all.
Based on your model, your inputs should be named as (never mind input types because only you know which ones should be rendered and as which type):
<!-- erste fähigkeit -->
<input name="Faehigkeit[0].Id" />
<input name="Faehigkeit[0].Name" />
<input name="Faehigkeit[0].Beschreibung" />
<input name="Faehigkeit[0].Subcategories[0].Id" />
<input name="Faehigkeit[0].Subcategories[0].Name" />
...
<input name="Faehigkeit[0].Choices[0].Id" />
<input name="Faehigkeit[0].Choices[0].Name" />
<input name="Faehigkeit[0].Choices[0].Beschreibung" />
<input name="Faehigkeit[0].Choices[0].Selected" />
...
<input name="Faehigkeit[0].Choices[x].Id" /> <!-- "x" could be any number -->
...
<input name="Faehigkeit[0].Parent" />
<!-- zwite fähigkeit -->
<input name="Faehigkeit[1].Id" />
...
<!-- usw. -->
Based on this complex hierarchical model this form can be huge. And you probably don't wish to send all properties because you only need some that are relevant. But the main thing is that input naming should be as described.
Extremely important: I should point out that indexes (ie. Faehigkeit[0]) are not supposed to relate to IDs, but are rather array/list item indexes. They should be consecutive so they should always start at 0 and should be incremented by 1 to the last N hence should have no gaps whatsoever. Believe me I've tested that when I was writing my blog post. Otherwise model binding on the server will fail. Make sure you pass this requirement! Based on your edited question this is not the case, because you're putting in IDs instead of indexes.
If you'd transfer your data as JSON to the client, you'd have an object on the client as:
var data = {
Faehigkeit: [
{
Id: 1,
Name: "Some name",
Beschreibung: "Some description",
Subcategories: [
{ ... },
{ ... },
...
],
Choices: [
{ ... },
{ ... },
...
]
},
{ ... },
...
]
};
And if your client side view manipulated this object directly you could then afterwards send it back to the server by means of jQuery Ajax call ie:
$.ajax({
type: "POST",
url: "someController/myAction",
data: $.toDictionary(data), // this is my jQuery plugin mentioned earlier
success: function() { ... },
error: function() { ... }
});
So the choice is yours, but in either case, field names should be correct otherwise your model won't be bound to your parameter and you won't be able to use it.
You could try with <%= Html.EditorFor(m => m); %> but that may not be the kind of form you'd like to use. In such case manual input naming would have to be used. Or you can as well write some custom code.
One of the ways would be to chaneg your partial views mode types to become for instance Tuple<string, Category>. String would tell it what string should be pre-pent to your field names.
Html.RenderPartial("Faehigkeit", Tuple.New("Faehigkeit[0]", item2));
And then when you go along, previous data should be always added so your subcategories' lists and their respected subobjects will have correct input form names.
Not nice, but I suppose it's the only way to make it.
Usually if you put the HttpPost as an attribute of your action, its something like:
[HttpPost]
public void myAction(NewHorseModel newHorseModel)
{;}
Then put the class type as your argument it should autobind it for you, if there is anything crazy that you need to do, or are posting from an external non ASP page to this action you can just use the FormCollection (i think) as your argument and thats a glorified Dictionary containing all your elements and values.