I have a controller which returns a view model that has a List<string> property, and displays the results in series of text boxes in a jQuery datatable. I want to allow the user to edit the list, and post the updated data back to the controller. This is working correctly if I stay on the first page of the data table, but if I navigate to the next page and then edit one of the fields, the List<string> property on the view model is an empty list in the controller action.
Here's my view model
public class ViewModel
{
public List<string> Values { get; set; }
public ViewModel()
{
this.Values = new List<string>();
}
}
My controller actions
public ActionResult Edit()
{
ViewModel viewModel = new ViewModel();
viewModel.Values.Add("ST0001");
viewModel.Values.Add("ST0002");
viewModel.Values.Add("ST0003");
viewModel.Values.Add("ST0004");
viewModel.Values.Add("ST0005");
viewModel.Values.Add("ST0006");
viewModel.Values.Add("ST0007");
viewModel.Values.Add("ST0008");
viewModel.Values.Add("ST0009");
viewModel.Values.Add("ST0010");
return View(viewModel);
}
[HttpPost]
public ActionResult Edit(ViewModel viewModel)
{
// I have a breakpoint here to examine the values of viewModel.Values
// As long as I stay on the first page of the data table, viewModel.Values contains the updated values I type
// If I go to the second page of the table, viewModels.Values is an empty list
return RedirectToAction("Edit", viewModel);
}
Here's the datatable in the view
<table id="myTable" class="table">
<thead>
<tr>
<th>
#Html.Label("Value")
</th>
</tr>
</thead>
<tbody>
#if (Model != null)
{
for (int i = 0; i < Model.Values.Count; i++)
{
<tr>
<td>
#Html.TextBoxFor(model => Model.Values[i])
</td>
</tr>
}
}
</tbody>
</table>
#section Scripts
{
<script type="text/javascript">
$(function () {
$('#myTable').dataTable({
'order': [[0, 'asc']],
'pageLength': 5
});
});
</script>
}
The <input>'s id looks correct on each page of the datatable.
Here is what an input control on the first page looks like
Here is what an input control on the second page looks like
I'm assuming the problem is that ASP won't load a List parameter to a controller if the submitted data starts from a non-zero index? Is there any way of getting around this, such as looking at all submitted form data from my controller and manually loading each array element into the appropriate position in my list?
Related
Trying to create a running total that will count calories for my website as part of a college assignment. Everything is finished apart from this one section which keeps on tripping me up. In theory it should take in a value and add it to a running total and display this total, but I think each time I press the button to calculate this it runs a new instance of the model I use to calculate this. There are 3 files interacting with each other for this operation
CalorieCount.cs - The model which contains the data and the calculation
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
namespace BlairMackenzie_CalorieCount.Models
{
public class CalorieCount
{
// Stores user input for calculating calorie count
[Required]
[Display(Name = "Calories Consumed")]
public int CalorieIntake { get; set; }
// Stores the running total
[Display(Name = "Total Calories Consumed Today")]
public int TotalCalorieCount { get; set; }
// This method calculates total calorie count
public void CalculateTotalCalorieCount()
{
// Add CalorieIntake to TotalCalorieCount
TotalCalorieCount += CalorieIntake;
}
}
}
CalorieCounter.cshtml - The webpage to display all this to the user and take an input
#{
ViewBag.Title = "CalorieCounter";
}
<h2>Calorie Counter</h2>
<br />
<hr />
#using (Html.BeginForm())
{
<div class="table-responsive">
<table class="table table-lg">
<tr>
<!-- Displays label and input box for CalorieIntake -->
<td>#Html.LabelFor(m => m.CalorieIntake)</td>
<td>#Html.EditorFor(m => m.CalorieIntake)</td>
</tr>
<tr>
<!-- Displays label and display for TotalCalorieCount -->
<td>#Html.LabelFor(m => m.TotalCalorieCount)</td>
<td>#Html.DisplayFor(m => m.TotalCalorieCount)</td>
</tr>
<tr>
<td></td>
<td>
<!-- Submit button triggers calculation -->
<input type="submit" value="Calculate New Total Calories" class="btn btn-lg btn-success" />
</td>
</tr>
</table>
</div>
}
HomeController.cs - handles the loading the page and calling for the model to handle the calculations
// Loads Calorie Count Page
// Sets up empty form
[HttpGet]
public ActionResult CalorieCounter()
{
return View();
}
// This action is called when the user clicks the submit button
// The completed form is sent to the back end
[HttpPost]
public ActionResult CalorieCounter(CalorieCount model)
{
if (ModelState.IsValid)
{
model.CalculateTotalCalorieCount();
}
return View(model); // Return the model to the view with all values calculated
}
If anyone can spot this issue and suggest a fix that would be great thanks.
There's some fundamental misunderstandings going on here. ASP.NET MVC is essentially a "stateless" system. So when you make a request from the browser to the server, everything on the server is brand-new, uninitialised memory. It's "newing-up" all objects that are used to process the request and send a response.
So if you have a bit of data that you want to persist between requests, you have a couple of options:
Make sure all the data you need is "round-tripped" each time. That will often mean you need hidden <input /> fields in your pages to contain that data, inside the form. As long as those inputs are setup correctly (I recommend using HtmlHelper, i.e. Html.HiddenFor<T>()) then the framework will match the value up and set that property of the model object in your POST handler.
Store data on the server, in a database. each request loads the current data, adds the new amount to it, and saves it, then displays whatever it needs to on the page.
UPDATE
After making a working MVC site from your example and replicating the problem, I did some research and found that the HtmlHelpers look at ModelState before looking at the model that was passed to the view. This is why the value is never updating. I also noticed that there was a validation error because the int TotalCalorieCount implied a value was required, and presumably 0 wasn't good enough.
So here's what I did to get it working:
Add #Html.HiddenFor(m => m.TotalCalorieCount) immediately before the submit button in the view.
Make CalorieCount.TotalCalorieCount int? rather than int (which removed the DataAnnotations-implied requirement to be present.
Added a call to ModelState.Clear() immediately inside the if (ModelState.IsValid) block in HomeController.
It now works as you'd expect for me.
HomeController.cs
// Loads Calorie Count Page
// Sets up empty form
[HttpGet]
public ActionResult CalorieCounter()
{
return View();
}
// This action is called when the user clicks the submit button
// The completed form is sent to the back end
[HttpPost]
public ActionResult CalorieCounter(CalorieCount model)
{
if (ModelState.IsValid)
{
ModelState.Clear();
model.CalculateTotalCalorieCount();
}
return View(model); // Return the model to the view with all values calculated
}
CalorieCount.cs
using System.ComponentModel.DataAnnotations;
namespace WebApplication1.Models
{
public class CalorieCount
{
// Stores user input for calculating calorie count
[Required]
[Display(Name = "Calories Consumed")]
public int CalorieIntake { get; set; }
// Stores the running total
[Display(Name = "Total Calories Consumed Today")]
public int? TotalCalorieCount { get; set; }
// This method calculates total calorie count
public void CalculateTotalCalorieCount()
{
// Add CalorieIntake to TotalCalorieCount
TotalCalorieCount = (TotalCalorieCount ?? 0) + CalorieIntake;
}
}
}
CalorieCounter.cshtml
#model CalorieCount
#{
ViewBag.Title = "CalorieCounter";
}
<h2>Calorie Counter</h2>
<br />
<hr />
#using (Html.BeginForm())
{
<div class="table-responsive">
<table class="table table-lg">
<tr>
<!-- Displays label and input box for CalorieIntake -->
<td>#Html.LabelFor(m => m.CalorieIntake)</td>
<td>#Html.EditorFor(m => m.CalorieIntake)</td>
</tr>
<tr>
<!-- Displays label and display for TotalCalorieCount -->
<td>#Html.LabelFor(m => m.TotalCalorieCount)</td>
<td>#Html.DisplayFor(m => m.TotalCalorieCount)</td>
</tr>
<tr>
<td></td>
<td>
<!-- Submit button triggers calculation -->
#Html.HiddenFor(m => m.TotalCalorieCount)
<input type="submit" value="Calculate New Total Calories" class="btn btn-lg btn-success"/>
</td>
</tr>
</table>
</div>
}
One last thing. If you don't want to make CalorieCount.TotalCalorieCount a nullable int, you could return a model to your view in the GET, instead, like this:
// Loads Calorie Count Page
// Sets up empty form
[HttpGet]
public ActionResult CalorieCounter()
{
return View(new CalorieCount() { TotalCalorieCount = 0 });
}
Which also fixes that part of the problem.
Ended up fixing this issue by creating a DbSet for the CalorieCount model and had this store every calculation made. The controller would loop through this database until it found the largest id showing it was the newest calculation and it will store the TotalCalorieCount in a local variable and add the CalorieIntake and send the local variable to the model so it can be displayed by the webpage.
I am trying to use an MVC form to modify some information in my database. I want to be able to select a few items from a table using a series check boxes. It should update the database boolean values when I hit a link at the bottom of my form.
So far, I have tried a few solutions from other threads, but since I am new to MVCs, they are rather confusing.
This is what I have right now for my HTML:
#foreach (var item in Model)
{
<tr>
#if (!item.IsCurated)
{
<td>
#Html.CheckBoxFor(modelItem => item.isChecked, new { #checked = true })
</td>
{
</tr>
#Html.ActionLink("Update", "updateDatabase", Model)
The "updateDatabase" method calls
public void updateDatabase()
{
db.SaveChanges();
}
I believe the changes to the database are being saved, but that the check boxes are not actually assigning any changed values.
So here's my situation.
Let's say I have a view called TheView.cshtml. TheView.cshtml has a ViewModel called TheViewModel.cs. In TheViewModel.cs, resides a List of an object (TheObject) called TheObjectList.
I have an editor template for TheObject called TheObject.cshtml. Using this editor template, I can simply display all of the items in the TheObjectList with #Html.EditorFor(model => model.TheObjectList).
However, now I want to add objects to this list dynamically. I have an AJAX function, which calls a simple partial view to give the user a blank row to add a new "TheObject", however, any new TheObject I add dynamically is not considered part of the original TheObjectList.
This is because each item in the original TheObjectList is created with a certain prefix based on its index in the original list, whereas each new dynamic TheObject is created without a prefix, thus Razor does not see it as part of the list.
Is there a way around this?
TheView.cshtml
#model Models.ViewModels.TheViewModel
<table id="Table">
<tbody>
#Html.EditorFor(m => m.TheObjectList);
</tbody>
</table>
<button id="AddObject" type="button" class="btn btn-primary">Add Object</button>
TheViewModel.cs
public class TheViewModel
{
public List<TheObject> TheObjectList { get; set; }
}
AddObject Controller Method
public IActionResult AddObject()
{
return PartialView("_EmptyRow", new TheObject());
}
AJAX Method to Add Object
$('#AddObject').click(function () {
$.ajax({
url: 'AddObject',
cache: false,
success: function (data) {
$('#Table > tbody').append(data);
},
error: function (a, b, c) {
alert(a + " " + b + " " + c);
}
});
});
You basically needs to generate/return markup which looks same as what the editor template generates for your form fields except for the element index . You need to pass the index from client side which will be a part of the form field name.
Let's assume your editor template looks like below and your TheObject has a GroupName property
#model TheObject
<div>
#Html.HiddenFor(x => x.GroupName)
#Html.TextBoxFor(s=>s.GroupName,new {#class="thingRow"})
</div>
Now when you render your page with your current code, Editor template will generate input fields like this
<input class="thingRow" id="TheObjectList_0__GroupName"
name="TheObjectList[0].GroupName" type="text" value="Something">
where 0 will be replaced with the index of the items in your TheObjectList collection.
Now let's say you already have 5 items in the collection, So when user clicks add button, you want to generate markup like above except 0 will be replaced with 5(for the sixth item). So let's update the ajax call to include the current number of items.
$('#AddObject').click(function () {
var i = $(".thingRow").length;
$.ajax({
url: 'AddObject?index=' + i,
success: function (data) {
$('#Table > tbody').append(data);
},
error: function (a, b, c) {
console.log(a, b, c);
}
});
});
That means, we need to accept the index value in our action method. Since we need to pass this index value from action method to our view to build the input field name value, I added a property to your class called Index
public ActionResult AddObject(int index)
{
return PartialView("_EmptyRow", new TheObject { Index = index});
}
Now in your _EmptyRow partial view,
#model TheObject
<input id="TheObjectList_#(Model.Index)__GroupName"class="thingRow"
name="TheObjectList[#(Model.Index)].GroupName" type="text" value=""/>
Now when you submit the form, model binding will work for these dynamically added items, assuming you have your Table inside a form.
#model TheViewModel
#using (Html.BeginForm())
{
<table id="Table">
<tbody>
#Html.EditorFor(m => m.TheObjectList);
</tbody>
</table>
<button id="AddObject" type="button" class="btn btn-primary">Add Object</button>
<button type="submit" class="btn btn-primary">Save</button>
}
First of I am a complete beginner at MVC. How would I be able to display data from the database in the events table in a partial view if a certain boolean field is true.
This is my partial view:
#model IEnumerable<TheBigEvent.Models.RecommendedEvents>
<table>
<tr>
<td>
#Html.DisplayNameFor(model => model.Event_Name)
</td>
<td>
#Html.DisplayNameFor(model => model.Event_Date)
</td>
</tr>
<tr>
#foreach (var item in Model) {
<td>
#Html.DisplayFor(modelItem => item.Event_Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Event_Date)
</td>
}
</tr>
</table>
This is my controller
public ActionResult _RecommendedEvents()
{
var recommendedevents = from Events in db.Database1
select Events;
recommendedevents = recommendedevents.Where(s => s.Recommended.Equals(true));
return PartialView("_RecommendEvents", recommendedevents);
}
And the Code for displaying the partialview
#Html.Partial("_RecommmndedEvents")
This is the error I am receiving
[EDIT]
public ActionResult _RecommendedEvents(RecommendedEvents model)
{
model = new RecommendedEvents();
var recommendedevents = from Events in db.Database1
select Events;
recommendedevents = recommendedevents.Where(s => s.Recommended.Equals(true));
return View(model);
}
#{
Html.RenderAction("view","controller")
}
This will go to the given controller and action that has to return a partialview with the correct model
object reference not set to an instance of an object has always been an un initialized list for me. try initializing recommendedevents before setting it. something like
List<Events> recommendedevents = new List<Events>();
replacing Events with whatever the type is.
the first parameter in Html.Partial is the partial name not the method call. you need to either pass the model to your view thought a view model and pass it to the partial
#Html.Partial("_RecommendedEvents", Model.Events)
or load the partial through an ajax call. see my answer here for an example How do I render a partial form element using AJAX
The #HTML.Partial() function does not go through any controller action to render, it just renders the View's HTML in that spot in the document. And you aren't passing in the IEnumerable<TheBigEvent.Models.RecommendedEvents> to that partial view, so when that partial renders, the Model is null.
Put the IEnumerable<TheBigEvent.Models.RecommendedEvents> object into your main page's View Model, or maybe on something in the ViewBag, and pass it to the partial view at the time you call the Partial method:
#HTML.Partial("_RecommmndedEvents", ViewBag.RecommendedEvents)
In the top-level page's controller action, set that ViewBag.RecommendedEvents just like how you are instantiating it in your controller code above.
The error means your model is null,
PartialView() is used when you are using Ajax, otherwise you can write your code as below :
return View("_RecommendEvents", recommendedevents);
i want to change grid date when the user change drop down values with ajax.
this is my C# code:
public ActionResult Index(string name)
{
ViewBag.Drop = db.Students.Select(r => r.Fname);
var model = from r in db.Students
where r.Fname == name
select r;
return View(model);
}
and this is cshtml file:
#using (Ajax.BeginForm("Index", new AjaxOptions
{
UpdateTargetId = "grid",
HttpMethod = "GET"
}
))
{
#Html.DropDownList("name", new SelectList(ViewBag.Drop));
<input type = "submit" value = "submit" />
}
<div id= "grid">
</div>
my problem is that when i change drop down values all of views are shown again. i don't want to see new view , just want to change grid data. how can i do that?
Do you have an action method which returns only the Grid data ? if not, create one
public ActionResult GridData(string name)
{
var gridItems=repo.GetCustomers(name).ToList();
//the above method can be replaced by your
// actual method which returns the data for grid
return View(model);
}
I am simply assumuing you have a Customer model with properties called FirstName and LastName and you want to show a list of Customers in the Grid. You may replace those with your actual class names and properties.
Make sure you have a view called GridData.cshtml which will render the HTML markup for your Grid
#model IEnumerable<YourNameSpace.Customer>
#{
Layout=null;
}
<table>
#foreach(var item in Model)
{
<tr><td>item.FirstName</td><td>item.LastName</td></td>
}
I would write simple (and clean) code like below instead of using Ajax.BeginForm
#Html.DropDownList("name", new SelectList(ViewBag.Drop));
<div id= "grid"></grid>
<script type="text/javascript">
$(function(){
$("select[name='name']").change(function(){
var url="#Url.Action("GridData","YourControllerName")"+"?name="+$(this).val();
$("#grid").load(url);
});
});
</script>