Capture textarea content when posting to Edit controller/view - c#

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.

Related

How to use EditorFor to save changes to the Model?

I have a view like this:
#model IEnumerable<Namespace.MyClass>
#using Newtonsoft.Json;
<form asp-action="Update">
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.Product)
</th>
<th>
#Html.DisplayNameFor(model => model.IsAvailable)
</th>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Product)
</td>
<td>
#Html.EditorFor(modelItem => item.IsAvailable)
</td>
</tr>
}
</tbody>
</table>
<div>
<a href=#Url.Action("SaveChanges", "Product", new {productList = JsonConvert.SerializeObject(Model) })> Save changes</a>
</div>
</form>
where SaveChanges looks like this:
[HttpGet] // Perhaps it should be HttpPost here, but if I change it to that nothing happens when I run the program after clicking the "Save changes" link
public ActionResult SaveChanges(string productList)
{
List<MyClass> products = JsonConvert.DeserializeObject<List<MyClass>>(productList);
// Here I continue with SqlCommand to later make an UPDATE to a database changing the value of IsAvailable which is the only adjustable value in my view
}
My issue is that when I run the program and I change IsAvailable (which is a bool) from false to true in my view and press "Save Changes", the model does not change, i.e. productList still has the value False for IsAvailable for that particular product.
Does anybody have an idea as to why? From my understanding if I have a and
Model information is not updated when you send the model to the action with the link. And the same initial amount is sent.
The changes you make to the model fields are only in the html controls and have nothing to do with the model. The model is updated when you post the form information.
I submitted the form to jQuery in the following code
in view
#model List<Namespace.MyClass>
#using (Html.BeginForm("SaveChanges", "Product", FormMethod.Post))
{
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.FirstOrDefault().Product)
</th>
<th>
#Html.DisplayNameFor(model => model.FirstOrDefault().IsAvailable)
</th>
</thead>
<tbody>
#for (int i = 0; i < Model.Count(); i++)
{
<tr>
<td>
#Html.DisplayFor(modelItem => Model[i].Product)
</td>
<td>
#Html.EditorFor(modelItem => Model[i].IsAvailable)
</td>
</tr>
}
</tbody>
</table>
<div>
#Html.ActionLink("Save changes", "", null, new { #id = "submit"})
</div>
}
#section scripts{
<script type="text/javascript">
$("#submit").click(function () {
document.forms[0].submit();
return false;
});
</script>
}
action in controller
[HttpPost] // Perhaps it should be HttpPost here, but if I change it to that nothing happens when I run the program after clicking the "Save changes" link
public ActionResult SaveChanges(IEnumerable<MyClass> products)
{
// Here I continue with SqlCommand to later make an UPDATE to a database changing the value of IsAvailable which is the only adjustable value in my view
}

I can't see the input value in the input in html controller

The model of my html page is in the form of a list. I want to enter the exam grade here, but when I entered the exam grade, my data does not go to my actionResult type function. In fact, when I write an integer value to the input, it does not save its value. The database also lists the values I have written manually, but still does not bring the data to the function.
my html code:
#model IEnumerable<OnlineBasvuru.Entity.ViewModel.ExamViewModal>
#{
Layout = null;
}
<div>
#using (Html.BeginForm("Save", "Exam", FormMethod.Post))
{
<table class="table">
<thead>
<tr>
<th>
StudentId
</th>
<th>
Note
</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.ToList())
{
<tr>
<td>
#Html.Label(item.StudentId.Value.ToString())
</td>
<td>
#Html.EditorFor(modelItem => item.Note)
</td>
</tr>
}
</tbody>
</table>
<button type="submit">Save</button>
}
</div>
my controller code:
[HttpPost]
public ActionResult Save(ICollection<ExamViewModal> nwModal) // Normally nwModal should be listed as a list of the model and the data should be in
{
foreach (ExamViewModal modal in nwModal)
{
EXAM exam = examService.Get(modal.Id);
exam.NOTE = modal.Note;
examService.Save(exam );
}
return RedirectToAction("ExamList", "Exam");
}
Please show me a way I do not know how to do. Thank you.
Try using a for loop instead of a foreach. This will then set the name property using indexes rather than spitting out the same name for each item in the loop.
For example:
for (int i = 0; i < Model.Count(); i++) {
Html.EditorFor(m => Model[i].Note)
}

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>

Posting Razor textarea back to controller

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.

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