Posting Razor textarea back to controller - c#

I have a ViewModel that wraps two lists of different objects: List<Review> Reviews and DayCommentary DayCommentary into ReviewViewModel. In my view, I group Reviews by Review.Country:
var groupedReviews = Model.Reviews.GroupBy(item => item.Country)
In the view, I loop through groupedReviews and build an HTML table for each group. My goal is to output each Review in group while still binding to the model for posting back to the controller. This is important because in the view I have a textarea field that needs to be pass entered text back to the controller during POST actions. I understand that this requires a for-loop rather than a foreach to maintain the binding.
I've thus tried the following, which displays correctly in the view but does not bind to the model during FormMethod.Post, for example during a Add/Edit/Delete controller action:
#using (Html.BeginForm("Save", "Review", FormMethod.Post))
{
foreach (var group in groupedReviews)
{
<h6>#Html.Raw(group.Key)</h6>
<table>
<tr>
<th style="text-align: center">
#Html.DisplayNameFor(model => model.Reviews[0].Ticker)
</th>
</tr>
#for (var i = 0; i < group.Count(); i++)
{
<tr>
<td style="text-align: center">
#Html.TextAreaFor(model => group.ElementAt(i).Commentary)
#Html.HiddenFor(model => group.ElementAt(i).Commentary)
</td>
</tr>
}
</table>
}
}
I'm pretty sure the problem here is with ElementAt(i), but what can I use instead when iterating through this type of grouped collection?
I've tried assigning name and id attributes to the TextArea field as suggested in answers to some similar questions to no avail.
I've been going in circles trying to use different types of loops and iterating over different collections but nothing achieves my desired goal.

Related

how to load xml node into html textboxfor

So I made a page for editing XML nodes but how exactly do I load the value from the node into the html.textboxfor
as I had been trying with
#Html.TextBoxFor(s => s.CarIsScrapped, new { #Value = CarIsScrapped}))
but then I get
CS0103: The name 'CarIsScrapped' does not exist in the current context
Now I can display or edit the nodes but can't do both as I either have to use
CarIsScrapped = node["CarIsScrapped"].InnerText = scrapped
for editing but then the textboxfor is empty
or CarIsScrapped = node["CarIsScrapped"].InnerText
for displaying but then I can't edit the node
my page
#using ActionLink_Send_Model_MVC.Models
#model IEnumerable<SettingsModel>
#{
Layout = null;
}
<body>
#using (Html.BeginForm("Index", "Home", FormMethod.Post))
{
foreach (SettingsModel setting in Model)
{
<table cellpadding="0" cellspacing="0">
<tr>
<th colspan="2" align="center"></th>
</tr>
<tr>
<td class="auto-style1">Name: </td>
<td class="auto-style1">
#Html.TextBoxFor(m => setting.CarIsScrapped)
</td>
</tr>
<tr>
<td> </td>
<td>
#Html.ActionLink("Submit", "", null, new { #id = "submit" })</td>
</tr>
<tr>
</tr>
<tr>
</tr>
<tr>
</tr>
</table>
}
}
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script type="text/javascript">
$(function () {
$("#submit").click(function () {
document.forms[0].submit();
return false;
});
});
</script>
</body>
controller
public class HomeController : Controller
{
// GET: Home
public ActionResult Index(SettingsModel setting)
{
List<SettingsModel> settings = new List<SettingsModel>();
string scrapped = setting.CarIsScrapped;
//Load the XML file in XmlDocument.
XmlDocument doc = new XmlDocument();
doc.Load(Server.MapPath("~/XML/Settings.xml"));
//Loop through the selected Nodes.
foreach (XmlNode node in doc.SelectNodes("Settings/UserSettings"))
{
//Fetch the Node values and assign it to Model.
settings.Add(new SettingsModel
{
CarIsScrapped = node["CarIsScrapped"].InnerText = scrapped
});
doc.Save(Server.MapPath("~/XML/Settings.xml"));
}
return View(settings);
}
}
Do not use foreach, because this will cause problems when you try to bind your inputs back to the list of models. You need to use a loop in here:
for (var i = 0; i < Model.Count(); i++) {
#Html.TextBoxFor(m => Model[i].CarIsScrapped)
}
Model binding to a List
The question has been totally changed to a new question. This is answer to the new question.
Change the model of the page into List<SettingsModel>. Then use a for loop to create the text boxes for editing the model. Also for the Post method, use a variable of type List<SettingsModel>.
Here is the code that you need to use:
#model List<SettingsModel>
#using (Html.BeginForm())
{
for (int i = 0; i < Model.Count; i++)
{
<div>
#Html.TextBoxFor(x => Model[i].CarIsScrapped)
</div>
}
<input type="submit" value="Save" />
}
There is a great article by Phil Haack about Model Binding to a List. Take a look at the article to learn more about editing non-sequential lists.
The name 'XXXX' does not exist in the current context
The question has been totally changed to a new question. This is answer to the old question.
#Html.TextBoxFor(s => s.CarIsScrapped, new { #Value = CarIsScrapped})) obviously will result in CS0103: The name 'CarIsScrapped' does not exist in the current context exception because in the second parameter of the method (new { #Value = CarIsScrapped}), the name CarIsScrapped is not defined. In fact it's a property name and you can not use it directly.
In fact using TextBoxFor, it's enough to use x => x.CarIsScrapped and the second parameter is not needed.
Usually when you receive The name doesn't exist in the current context, you can check for these situations:
You haven't defined a variable with that name.
You misspelled the variable name.
The namespace that defines a class is missing.
Your project needs to add a reference to the dll contains that type.
In this case you are using CarIsScrapped like a variable and it seems this variable doesn't exists in current context.
The correct line of code should be:
#Html.TextBoxFor(s => s.CarIsScrapped)
And also, in the action, you need to pass the model to the page, unless you will see an empty text box.
The problem is that your Model on the View is a IEnumerable. The Helpers use reflection to determine the prefix, name and ID that gets assigned to the TextBox when it get's rendered into html. Since you are using a foreach and looping through your IEnumerable, the name and id will be identical for each of your TextBoxes. Take a look at your rendered HTML and see what they look like. On the post back, they will all have the same name, so will get Concatenated into a list of comma separated values. The model binder will not be able to deserialize that into your original IEnumerable model. Take a look at the Request.Params
You should either create Model that has your list of SettingsModel as a List property, then use an index accessor in your Razor to do the Html.TextBoxFor. If that is not an option, set your model as List and access it via index vs using a foreach.
Like so:
#using ActionLink_Send_Model_MVC.Models
#model List<SettingsModel>
#{
Layout = null;
}
<body>
#using (Html.BeginForm("Index", "Home", FormMethod.Post))
{
for(var idx = 0; idx < Model.Count();idx++) {
{
<table cellpadding="0" cellspacing="0">
<tr>
<th colspan="2" align="center"></th>
</tr>
<tr>
<td class="auto-style1">Name: </td>
<td class="auto-style1">
#Html.TextBoxFor(m => Model[idx].CarIsScrapped)
</td>
</tr>
<tr>
<td> </td>
<td>
#Html.ActionLink("Submit", "", null, new { #id = "submit" })</td>
</tr>
<tr>
</tr>
<tr>
</tr>
<tr>
</tr>
</table>
}
}
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script type="text/javascript">
$(function () {
$("#submit").click(function () {
document.forms[0].submit();
return false;
});
});
</script>
</body>

Getting back null model after post [duplicate]

This question already has answers here:
Post an HTML Table to ADO.NET DataTable
(2 answers)
Closed 4 years ago.
It seems like a lot of people have this issue, but I haven't found a solution to my specific case yet.
I start by sending my tickets to my view
[HttpGet]
public ActionResult AddPopcorn()
{
List<Ticket> tickets = new List<Ticket>();
// Fill the list with tickets
return View("AddPopcorn", tickets);
}
In the view I display the tickets with a form and checkbox. The view displays the tickets correctly.
#model List<Domain.Entities.Ticket>
#{
ViewBag.Title = "AddPopcorn";
}
<body>
// Html stuff
#using (Html.BeginForm("AddPopcorn", "Ticket", FormMethod.Post))
{
<table>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.TicketID)
</td>
<td>
#Html.DisplayFor(modelItem => item.Price)
</td>
<td>
#Html.DisplayFor(modelItem => item.TicketType)
</td>
<td>
#Html.CheckBoxFor(modelItem => item.Popcorn)
</td>
</tr>
}
</table>
<input type="submit" value="Voeg toe!" />
}
</div>
</body>
If people check the checkbox, I want the value 'Popcorn' to be true. So I want the view to return a list of tickets with updated values for popcorn based on the checkbox.
[HttpPost]
public ActionResult AddPopcorn(List<Ticket> model)
{
foreach (var item in model)
{
if (item.Popcorn == true)
{
item.Price = item.Price + 5;
}
}
return RedirectToAction("Payment", "Payment");
}
However, the model returned to AddPopcorn is null. I can't seem to figure out why.
Try changing your foreach loop to a for:
for (int i = 0; i < Model.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(x => Model[i].TicketID)
</td>
<td>
#Html.DisplayFor(x => Model[i].Price)
</td>
// etc
<td>
#Html.CheckBoxFor(x => Model[i].Popcorn)
</td>
</tr>
}
The default model binder uses a specific convention to work out how to bind a list of items, such as Model[0].Popcorn (name). You could check if the HTML for the CheckBox has a name attribute set to that format.
Other than using for, you could also specify a custom EditorTemplate for your Ticket object.
This is because your form isn't actually posting any data back to the controller.
You need input elements to actually be able to retrieve data from the form.
Instead, on the controller, access the model this way:
var model = this.Model
The form will only send data that you've got in input tags, e.g.
<input type="text" name="first_name" />

Bind checkbox and textbox to viewmodel [duplicate]

This question already has answers here:
Post an HTML Table to ADO.NET DataTable
(2 answers)
Closed 5 years ago.
I have to bind checkbox value and textbox value with model.Here I am using model to retrive and to post data as well.
I have tried few options but it didnt work.
Below is my code:
#model IEnumerable<ALDS.Web.Areas.AR.Models.ReportViewModel>
#{
ViewBag.Title = "Standings";
}
<table class="table table-bordered">
<thead>
<tr>
<th>LR Date</th>
<th>LR Line No</th>
<th>Issue</th>
<th>Correction Response</th>
<th>Remarks</th>
<th>Submission</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
#item.InsertDate
</td>
<td>
#item.LRLineID View
</td>
<td>Margin % incorrect</td>
<td><label for="cbox1">Corrected</label> <input type="checkbox" name="Corrected" value="#item.Status" /> <label for="cbox1">Neglected</label> <input type="checkbox" name="Neglected" value="#item.Status"/></td>
<td><input type="text" value="#item.Comments"/></td>
<td>Submit</td>
</tr>
}
</tbody>
</table>
I have to send the checkbox and textbox value to controller.
Please help.
Render the inputs using the HtmlHelpers provided by MVC. They will make sure that the id and name attributes of the generated <input/> are in a format that can be processed by the MVC ModelBinder.
Also, to post back lists of objects, use a for loop, so that the items get an index understood by the ModelBinder.
Wrap the inputs in a <form> to be posted to the Controller. With the following example, the model will be posted to the "Update" action in "ErrorController" when the user clicks the "Submit" button. myFormClass and myFormId are not neccessary, I just wanted to show how you could add them if needed.
#using(Html.BeginForm("Update", "Error", FormMethod.Post, new { #class = "myFormClass", id = myFormId })) {
for (var i = 0; i < Model.Length; i++) {
#Html.LabelFor(m => m[i].Status)
#Html.CheckBoxFor(m => m[i].Status)
#Html.LabelFor(m => m[i].Comments)
#Html.TextAreaFor(m => m[i].Comments) // multi line
#Html.TextBoxFor(m => m[i].Comments) // single line
}
<button type="submit">Submit</button>
}
LabelFor will try to find [Display(Name="some_resource_key", ResourceType = typeof(Resources))] attributes on the property in the ViewModel to look up the translated text to be used as label in the Resources.resx.
EDIT As Antoine mentioned, you have to provide inputs for ALL ViewModel properties that shall be posted back. You can render <input type="hidden"/> using #Html.HiddenFor(m => m[i].Id).

Capture textarea content when posting to Edit controller/view

I have a model of type List<ReviewGroupViewModel> where ReviewGroupViewModel is:
public class ReviewGroupViewModel
{
public string Country { get; set; }
public List<Review> Reviews { get; set; }
}
In my Index.cshtml view, I iterate through this model using nested for-loops and build a table for each ReviewGroupViewModel, grouped by ReviewGroupViewModel.Country. I ultimately have a row in my table for each Review object. The Commentary field for each row is displayed using a TextAreaFor HTML helper, allowing users to enter text:
Index.cshtml
#using (Html.BeginForm("Save", "Review", FormMethod.Post))
{
for (var i = 0; i < Model.Count; i++)
{
<h6>#Html.DisplayFor(m => m[i].Country)</h6>
<table class="table table-bordered table-condensed">
<tr>
<th style="text-align: center">
#Html.DisplayNameFor(m => m[i].Reviews[0].Commentary)
</th>
<th style="text-align: center">
Actions
</th>
</tr>
#for (var j = 0; j < Model[i].Reviews.Count; j++)
{
<tr>
<td style="text-align: center">
#Html.TextAreaFor(m => m[i].Reviews[j].Commentary)
</td>
<td style="text-align: center">
#Html.ActionLink("Edit", "Edit", new { tempId = Model[i].Reviews[j].TempId }) |
#Html.ActionLink("Delete", "Delete", new { tempId = Model[i].Reviews[j].TempId })
</td>
</tr>
}
</table>
}
}
This is bounded by a form that is submitted on-click of a "save" button elsewhere on the page.
Now let's say a user types some text into one (or many) of the textareas in the Index view and then proceeds to click on "Edit" in the "Actions" for a given table row. This text is then lost as I'm simply passing an Id (type: int) to my Edit controller method. The question is, how do I not lose this entered text (for both this Review object and all others in the Index view) when navigating to other views for actions like editing/deleting?
AFAIK, you can't directly pass a complex object from a view to a controller method. You also obviously can't nest HTML forms. Surely this is a common scenario but how do I handle it in code?
Your problem is the ActionLink. It generates a simple hyperlink. A hyperlink will send a GET request to the server. And you cannot put complex objects in the body of a GET request.
You need to do this:
Form with 2 submit buttons/actions
The code inside your 'for' loop should be placed into an editor template.
#using (Html.BeginForm("Save", "Review", FormMethod.Post))
{
for (var i = 0; i < Model.Count; i++)
{
#Html.EditorFor(m => Model[i], "MyTemplateName");
}
}
Create a folder named EditorTemplates inside your View folder and create a View called MyTemplateName. Inside it you have the code for each single item of the iteration by passing a single model to the view.
MyTemplateName.cshtml
#model Review
<h6>#Html.DisplayFor(m => m.Country)</h6>
<table class="table table-bordered table-condensed">
<tr>
<th style="text-align: center">
#Html.DisplayNameFor(m => m.Reviews[0].Commentary)
</th>
<th style="text-align: center">
Actions
</th>
</tr>
#for (var j = 0; j < m.Reviews.Count; j++)
{
// Here you should have different editor template... see the pattern :)
#Html.EditorFor(m => m.Reviews[j], "MySecondTemplate")
}
</table>
Hope this information can help you.

Failing to pass data from view to the Action by Html.BeginForm()

I am very new at asp.net mvc, so the reason behind my failure might be something basic as well, but I can't seem to find it after nearly a days work now.
What I am trying to do is to get the edited Model from the Index view and pass it to a second action which does not have view and returns return RedirectToAction("Index") in the related controller. In OrdersItemsController my Action is as the following:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult MarkedShipped(IEnumerable<orders_items> orderItems)
{
if (ModelState.IsValid)
{
foreach (var item in orderItems)
{
unitOfWork.GenericRepository<orders_items>().Update(item);
}
}
return RedirectToAction("Index");
}
And in the Index.cshtml which is in OrdersItems folder in the Views, what I did is as following:
#model IEnumerable<Project.DataAccess.orders_items>
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#using (Html.BeginForm("MarkedShipped", "OrdersItems", new { orderItems = Model }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.quantity)
</th>
<th>
#Html.DisplayNameFor(model => model.itemprice)
</th>
<th>
#Html.DisplayNameFor(model => model.trackingnumber)
</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.quantity)
</td>
<td>
#Html.DisplayFor(modelItem => item.itemprice)
</td>
<td>
#Html.EditorFor(modelItem => item.trackingnumber)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.itemid })
</td>
</tr>
}
</table>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="MarkShipped" class="btn btn-default" />
</div>
</div>
}
My problem is, I am not able to get the Model from the view with orderItems parameter, I am not sure if this is the right "syntax" for what I am trying to accomplish; but what I get for orderItems when the action is called is a List of orders_items with Count = 0 not a null value.
I have also checked if there is an application level exception, but got no exception from Application_Error in Global.asax
I am stuck for a day now so If anyone can point me a direction on how to pass the Model (or "the edited data") to the action for me to update the db, I would be very grateful. Thanks.
Firstly you cannot add a model which is a collection (or a model which contains a property which is a complex object or collection) to a forms route parameters. Internally the helper calls the .ToString() method and if you inspect the html generated for your form tag you will see something like
<form action=OrdersItems/MarkedShipped?orderItems=System.Collection.Generic.......
and binding will fail since you collection cannot be bound to a string. Even if it did work, it would be rather pointless because it would just post back the original values of the model.
Next, you cannot use a foreach loop to generate form controls. Again if you inspect the html your generating you will see that the EditorFor() method is generating inputs with duplicate id attributes (invalid html) and duplicate name attributes which do not have the necessary indexers to bind to a collection when you post. You need to use either a for loop of a custom EditorTemplate for typeof orders_items
Using a for loop means that your model must implement IList<T> and the view needs to be
#model IList<Hochanda.CraftsHobbiesAndArts.DataAccess.orders_items>
#using (Html.BeginForm()) // only necessary to add the controller and action if they differ from the GET method
{
....
#for(int i = 0; i < Model.Count; i++)
{
#Html.DisplayFor(m => m[i].quantity)
....
#Html.EditorFor(m => m[i].trackingnumber)
}
....
}
Using an EditorTemplate, create a partial in /Views/Shared/EditorTemplates/orders_items.cshtml (note the name of the file must match the name of the class)
#model Hochanda.CraftsHobbiesAndArts.DataAccess.orders_items
<tr>
<td>#Html.DisplayFor(m => m.quantity)</td>
....
<td>#Html.EditorFor(m => m.trackingnumber)</td>
....
</tr>
and then in the main view (you can use IEnumerable<T>)
#model IEnumerable<Hochanda.CraftsHobbiesAndArts.DataAccess.orders_items>
#using (Html.BeginForm())
{
<table class="table">
<thead>
....
<thead>
<tbody>
#Html.EditorFor(m => m)
</tbody>
</table>
....
}
The EditorFor() method accepts IEnumerable<T> and will generate one row for each item in you collection based on the html in the EditorTemplate
In both cases, it you inspect the html you will now see the correct name attributes necessary for you model to bind when you post
<input type="text" name="[0].trackingnumber" ... />
<input type="text" name="[1].trackingnumber" ... />
<input type="text" name="[3].trackingnumber" ... />
Try For instead of foreach here.
#for (int i = 0; i < Model.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.quantity)
</td>
<td>
#Html.DisplayFor(modelItem => item.itemprice)
</td>
<td>
#Html.EditorFor(modelItem => item.trackingnumber)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.itemid })
</td>
</tr>
}
Check this :- http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/
You can do the following
for (int i = 0; i < #ViewBag.Persons; i++)
{
<li>
<table>
<tr>
<td>
#Html.LabelFor(m => m.Fullname,new { #id = "FullnameLabel" + #i })
</td>
<td>
#Html.TextBoxFor(m => m.Fullname, new { #id = "Fullname" + #i })
</td>
</tr>
In this way each html element gets its own unique id and its possible to retrieve the information back from them individually. You can use for or Foreach. Just make sure that your razor iterator is functioning. You can view the element ids in the source to see if it is working.

Categories