I have a model for a batter average web app. After entering the player’s name and how many games in a series to average, I wish to collect hits
and atbats
for each game. There is a class called Game
with 2 properties, hits
, and atbats
(both integers). In the batter model, there is a property that is a List
of type Game
called series
.
I wish to display two text boxes, bound to a local instance of Game.hits
and Game.atbats
. I want to cycle through the game series length
(int) using the same text boxes on the screen until series has the entire list of hits
and atbats
.
The problem is trying to accomplish this task and set the List series
. I would prefer to have the Action AddGame
do that, but when I try to repeatedly call the same View and Action, the length
value doesn’t decrement. I tried decrementing length in the Action and it would only drop by 1 number and stop. The Hidden’s are necessary, I have found, in order to send their values to the Action.
Currently, the action is empty, so I haven’t included any code for it.
In the Models
folder, I have these two classes in one namespace:
public class Batter
{
public double average { get; set; }
public string ?name { get; set; }
public int length { get; set; }
public List<Game> series { get; set; } = new List<Game>();
}
public class Game
{
public int hits { get; set; }
public int atbats { get; set; }
}
This is my view:
@model batterup.Models.Batter
@using batterup.Models
@{
ViewData["PageTitle"] = "Add Game";
}
<div>
<h1>@ViewData["PageTitle"]</h1>
<span>For player @Model.name </span>
<span> with a game series of @Model.length.</span>
@using (Html.BeginForm("AddGame", "Home", FormMethod.Post))
{
var game = new Game();
@Html.HiddenFor( model => model.name );
@Html.HiddenFor( model => model.length );
@Html.HiddenFor( model => model.series );
@Html.TextBox("hits");<br/>
@Html.TextBox("atbats");<br/>
<button type="submit">Add</button>
}
}
</div>
This view does display two textboxes and one button correctly.
How do I conditionally render only this each time I wish to add a new Game
object to series
?
For the record, I am running this on RedHat Linux, Google Chrome, .NET Core, with Visual Studio Code. I am trying to use as few special Helper codes as possible. I’m only using @Html
because it works reliably and easily. Would prefer to use HTML <input>
.
I have tried repeatedly calling the action and view, but things don’t work out. I have used a for
loop around my text box and submission code. That works, but displays length
number of textboxes. I don’t want that. I want only the one set of text boxes and one submit button.
I am expecting that when a user presses the Add Game submit button that the Game object is added to the series list and returned back to ( or a new set of ) two text boxes to get the next Game object’s values and the user presses Add Game. The user never sees more than two text boxes and the submit button on the page. When a decrementing ‘length’ value reaches zero, or any other usable counter expires, then we go off and average the values in the list.
I have searched for this for several days and can’t find an exactly similar problem where someone solved it. I know with conditional rendering in Reactjs this would be fairly straightforward, but I am trying to improve my skill with C# and ASP.NET MVC.
UPDATE: SOLVED 07/08/2023
After quite a bit of work and study, I have a possible answer on my own. My thanks to MStodd
for patience and help.
When I learned my problem was with the lack of an obvious state mechanism, I began to research how .NET Core provided state. It is done through a service called Session
. I practiced with Ben Cull’s example.
This is a minimalist example. I can scale this up to run a batter’s average app without using an array/list.
My Model:
public class ViewModel
{
public int mynumber {get;set;}
}
My Index Action:
public IActionResult Index()
{
HttpContext.Session.SetInt32("AtBats", 0);
return View(mymodel);
}
My Totalizing Action:
[HttpPost]
public IActionResult Totalize(ViewModel model)
{
if (ModelState.IsValid)
{
//You will still get a null warning, but it works
int atbats = (int)HttpContext.Session.GetInt32("AtBats");
atbats += model.mynumber;
HttpContext.Session.SetInt32("AtBats", atbats);
//necessary to view Session value in View (without extra View code)
ViewBag.AtBats = HttpContext.Session.GetInt32("AtBats");
}
return View("Index");
}
My Index View (all that’s needed for now):
@model app.Models.ViewModel;
@{
ViewData["Title"] = "Home Page";
ViewBag.PageTitle = "Testing";
}
<div>
<h1>@ViewBag.PageTitle</h1>
@using (Html.BeginForm("Totalize", "Home", FormMethod.Post))
{
@Html.TextBoxFor(model => model.mynumber)
@Html.ValidationMessageFor(model => model.mynumber)
<button type="submit">Add</button>
}
@if(ViewBag.AtBats != null){
<p>At Bats: @ViewBag.AtBats</p>
}
</div>
These Actions
and View
produce a total in ViewBag.AtBats
from whatever number I place in the TextBoxFor
and then submit. Repeatedly adding a number in the web page and clicking submit cycles through the Totalize
Action and adds up the total that displays in the web page.
I will post the batter’s average app code that descends from this as my answer.
2
Answers
MStodd
began heading me in the right direction with a suggestion to look atHttpContext
. I recognized this class when I began searching for how .NET Core MVC handles state, specifically state variables. I use these often inReact
when I utilizeuseState()
. I needed something similar to hold variables across page (View()
) changes. I decided not to keep the array of games since I also abandoned it in theReact
version. The key thing is that state variables are held inHttpContext.Session
class using several methods (e.g..SetString()
,.SetInt32()
).My Model:
My Index Action:
My AddGame Action (called repeatedly to add hits and at bats from games):
Note: The (int) cast to
HttpContext.Session.GetInt32()
avoids an error trying to assign a nullable int to a non-nullable int. VSCode will still give you a warning as does .NET, but because there isn't a null value, it works.My Index View (used during Index action and AddGame action):
There is a final action and View, but I didn't see it as germane to the problem of cycling through the same action while keeping and modifying variable state.
If that’s the model you’re binding to, you need to make sure all your data is in there. ASP.NET MVC has special list binding syntax, so you could do what I have below.
You could also change your model so that you’re just binding to the two values from the boxes and have a hidden ID to look up the already persisted player data; I think that’s probably what you were saying but not what your code was.
Make sure to "View Source" and look at what’s generated, and set a breakpoint in your controller actions and look at the HttpContext.Current.Request.Form, it might clear up some things.