ASP.NET-MVC - Website running total function not working - c#

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.

Related

Bind jQuery datatable values to controller parameter

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?

Get HTML check box to toggle MVC model boolean

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.

How to add an item to a list in a ViewModel using Razor and .NET Core?

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>
}

Value passed NULL from a View to another in ASP MVC 3

I am developing an ASP .Net MVC 3 application using C# and SQL Server 2005.
I am using also Entity Framework and Code First Method.
In a view Index, I have a DropDownList Gamme. I define its item selected in my view, like this :
public string SelectedProfile_Ga { get; set; }
In this view, I have a button Appliquerthat took me to another view Application.
<input type="button" value="Appliquer" id="appliquer" onclick="window.location = 'ProfileGa/Application'"/>
In the view Application, I have a button submit Appliquer.
<input type="submit" value="Appliquer" id="appl" />
When I click on Appliquer, I want save the value selected in my DropDownList Gamme in my base.
The problem is that this value is passed NULL when i change the view (exit page Index and open Application).
I find that with Debugging.
The Controller action :
[HttpPost]
public ActionResult app(FlowViewModel model)
{
Famille fam = new Famille();
fam.ID_Gamme = model.SelectedProfile_Ga;
db.Familles.Add(fam);
db.SaveChanges();
return RedirectToAction("Application");
}
Note :
I didn't forget this in the Application:
<% using (Html.BeginForm("app", "ProfileGa")) { %>
ProfileGa is the name of my controller.
For starters, your dropdown is in the Index view, and the selection is happening there. Then you're redirecting to ProfileGa/Application and leaving this information behind.
I would change this button:
<input type="button" value="Appliquer" .. etc
to a <submit>, and wrap the code with the dropdown in one of these:
using (Html.BeginForm("Application", "ProfileGa")) {
and add a Post version of Application
[HttpPost]
public ActionResult Application(FlowViewModel model)
{
// Do whatever
return View(model);
}
Then when you get to the Application view, it should still have the same information as it left Index with.
To check this is working, put a breakpoint at return View(model); and look at the model's contents.
However, posting null from the view probably means that something is wrong inside your <% using (Html.BeginForm("app", "ProfileGa")) { %> statement, so if the above doesn't do anything, post the code from your `Application' view.

How to change drop-down list on select

I have a dropdownlist that is being populated by a sql server, I am using Visual Studio 2010, cshtml, with razor as well as using the MVC pattern to create this project. What I am trying to do is when someone selects a value from the dropdown list on change it will update the page with information about that book.
I need help with the three things below:
user selects a book from the dropdownlist how to get the Book Name back to the controller
The server (retrieve the information from the server about the book) and
Back to view to be displayed.
I started with getting the dropdown poplulated.
My View looks like this
BookName: #Html.DropDownList("BookName", ViewData["BookName"] as IEnumerable<SelectListItem>, new { id = "UserSelectedValue" })
My Controller:
public ActionResult Index()
{
ViewData["BookName"] = new SelectList(_context.BookName.Select(a => a.Book_Name).Distinct());
return View();
}
A dropdown list can't cause the page to post back to your controller on its own. You need to do one of two things:
Add a submit button so that the user changes the dropdown and then clicks a button to view the results.
Use javascript to submit the form on the element's change event.
Either way, you will need to wrap the dropdown/submit button in a form.
Option 1
<form>
BookName: #Html.DropDownList("BookName", ViewData["BookName"] as IEnumerable<SelectListItem>, new { id = "UserSelectedValue" })
<input type="submit" value="Show results" />
</form>
Option 2
<script type="text/javascript">
// assuming you're using jQuery
$(function() {
$('#UserSelectedValue').change(function() {
$(this).parent('form').submit();
});
});
</script>
<form>
BookName: #Html.DropDownList("BookName", ViewData["BookName"] as IEnumerable<SelectListItem>, new { id = "UserSelectedValue" })
<input type="submit" value="Show results" />
</form>
Your controller code would then become something like:
public ActionResult Index(string bookName)
{
ViewData["BookName"] = new SelectList(_context.BookName.Select(a => a.Book_Name).Distinct());
if (!string.IsNullOrWhiteSpace(bookName))
{
ViewData["Books"] = _context.BookName.Where(b => b.Book_Name == bookName).ToList();
}
return View();
}

Categories