I'm trying to write a simple numbers checking program. You enter a correct next digit - it shows you next one, if not - nothing happens. In the Controller I'm trying to: set a value of variable, increment it, pass to specific View (howManyDigits). I know that I can make howManyDigits a session variable but the point here is to understand why the number is still going back to eg. "2" which was entered in View1 just after the first run of the app.
PiController:
namespace PI.Controllers
{
public class PiController : Controller
{
[HttpGet]
public ActionResult View1()
{
return View();
}
[HttpPost]
public ActionResult View2(Pinumber number)
{
if (number.tabPi[number.howManyDigits] != number.numberEntered)
number.howManyDigits++;
return View(number);
}
}
}
View1:
#model PI.Models.Pinumber
#using (Html.BeginForm("View2", "Pi", FormMethod.Post))
{
#Html.TextBoxFor(number => number.howManyDigits,new { autofocus = "autofocus"})
<input type="submit" value="How many numbers do you know?" />
}
In this view I have BeginForm and i'm trying to pass this variable using HiddenFor and Lambda expression back to the same Controller.
View2:
#model PI.Models.Pinumber
<div>
3.#for (int i = 0; i < Model.howManyDigits; i++)
{
#Model.tabPi[i]
}
</div>
#using (Html.BeginForm("View2", "Pi", FormMethod.Post))
{
#Html.HiddenFor(x=> x.howManyDigits)
#Html.TextBoxFor(x => x.numberEntered, new { autofocus = "autofocus" })
}
Model class Pinumber:
namespace PI.Models
{
public class Pinumber
{
public char[] tabPi { get; set; } = new char[100000];
public Pinumber()
{
for (int i = 0; i < 20; i++)
{
tabPi[i] = piNumber[i];
}
}
public int howManyDigits { get; set; }
public char numberEntered { get; set; }
public string piNumber = "141592653589793238462"
Unfortunately it's going back to Controller with an old value which i entered in other View1 which is used just to display the start site with entry number of digits that you already know - [HttpGet].
Things are not working that way.
Look, if you increment an value of some int variable, from 1 to 2 let's say, and you pass it to view. Then you want to increment it from 2 to 3, you need to send that 2 from View(from HTML code) back to the backend, and increment it there, otherwise, value is lost.
Related
My view model contains list of items. The list items are displayed by #Html.CheckBoxFor for user interaction. Initial set-up (i.e. checking values last selected in previous session) as well as manual selection and de-selection work correctly and bind back to the view model.
Function "Uncheck All" was added to the page recently. It sets each list item value IsSelected to false, but all items remains unchanged on the page.
When checking the ModelState.Values, the AttemptedValue of corresponding items got changed to "true\false" instead of "false". Am I missing any binding operation?
Quick summary of the main code parts:
View Model:
public class RuleDetail
{
public List<DataSourceCheckList> dataSourceCheckList { get; set; }
// many other items, methods etc...
}
View code:
#for (int n = 0; n < Model.dataSourceCheckList.Count; n++)
{
#Html.DisplayFor(model => model.dataSourceCheckList[n].DataSourceName)
#Html.HiddenFor(model => model.dataSourceCheckList[n].DataSourceName)
#Html.CheckBoxFor(model => model.dataSourceCheckList[n].IsSelected)
#Html.HiddenFor(model => model.dataSourceCheckList[n].DataSourceId)
}
The class from the list:
public class DataSourceCheckList
{
public int DataSourceId { get; set; }
public string DataSourceName { get; set; }
public bool IsSelected { get; set; }
public int columnCount = 0; //captures DataSetColumns count (0 migt mean the data source haven't been audited)
}
And the Post controller action:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Builder(int? id, ViewModels.RuleDetail rd, string ignoreChecklists, string command, int? callerId, string callerController, string callerAction)
{
//some other logic...
switch (command)
{
//bunch of other command values
case "UnselectSources":
foreach (DataSourceCheckList i in rd.dataSourceCheckList)
{
i.IsSelected = false;
}
break;
}
//update of other view model fields based on actions above
return View(rd);
}
Please, do you see any options, how to ensure the values in RuleDetail.dataSourceCheckList are displayed correctly?
Issue was driven by model state. Applying ModelState.Clear() to [HttpPost] controller action eliminated the true\false state observed before and values updated during the post action (including the checkbox mentioned in the original questions) are displaying correctly now.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Builder(int? id, ViewModels.RuleDetail rd, string ignoreChecklists, string command, int? callerId, string callerController, string callerAction)
{
//some other logic...
switch (command)
{
//bunch of other command values
case "UnselectSources":
foreach (DataSourceCheckList i in rd.dataSourceCheckList)
{
i.IsSelected = false;
}
break;
}
//update of other view model fields based on actions above
ModelState.Clear();
return View(rd);
}
So I'm new to ASP and EF and I am wondering how to do this incredibly basic operation, as well as a few questions to go along with doing it.
Currently I have a table we will call Resource;
class Resource
{
int current;
int min;
int max;
};
Right now I have the default CRUD options for this. What I would like is a + / - button on the main list that will manipulate the current value of each resource and update the value in the DB and on screen.
There are also certain operations I'd like to run such as "AddFive" to a selected group of resources.
So my questions;
How do I do this?
Is this scalable? If someone is constantly hitting the buttons this is obviously going to send a lot of requests to my DB. Is there any way to limit the impact of this?
What are my alternatives?
Thanks.
Edit:
To clarify the question; here are my post functions. How / where do I add these in my view to get a button showing for each resource. I just want the action to fire and refresh the value rather than redirect to a new page.
#Html.ActionLink("+", "Increment", new { id = item.ID })
public void Increment(int? id)
{
if (id != null)
{
Movie movie = db.Movies.Find(id);
if (movie != null)
{
Increment(movie);
}
}
}
[HttpPost, ActionName("Increment")]
[ValidateAntiForgeryToken]
public ActionResult Increment([Bind(Include = "ID,Title,ReleaseDate,Genre,Price")] Resource resource)
{
if ((resource.Current + 1) < (resource.Max))
resource.Current++;
return View(resource);
}
It sounds like the main issue you are having is creating a list of movies on the front end and updating the details for a specific one.
The key here is that you will need to either wrap a form around each item and have that posting to your update controller or use ajax / jquery to call the controller instead.
I have given you an example of the first one. Once the update controller is hit it will redirect to the listing page which will then present the updated list of movies.
Below is a minimal working example of how to wire this up. I've not included any data access code for brevity but have included psuedo code in the comments to show you where to place it.
Please let me know if you have any futher questions.
Controller
public class MoviesController : Controller
{
public ViewResult Index()
{
// Data access and mapping of domain to vm entities here.
var movieListModel = new MovieListModel();
return View(movieListModel);
}
public ActionResult Increment(IncrementMovieCountModel model)
{
// Put breakpoint here and you can check the value are correct
var incrementValue = model.IncrementValue;
var movieId = model.MovieId;
// Update movie using entity framework here
// var movie = db.Movies.Find(id);
// movie.Number = movie.Number + model.IncrementValue;
// db.Movies.Save(movie);
// Now we have updated the movie we can go back to the index to list them out with incremented number
return RedirectToAction("Index");
}
}
View
#model WebApplication1.Models.MovieListModel
#{
ViewBag.Title = "Index";
}
<h2>Some Movies</h2>
<table>
<tr>
<td>Id</td>
<td>Name</td>
<td>Increment Value</td>
<td></td>
</tr>
#foreach (var movie in Model.MovieList)
{
using (Html.BeginForm("Increment", "Movies", FormMethod.Post))
{
<tr>
<td>#movie.Id #Html.Hidden("MovieId", movie.Id)</td>
<td>#movie.Name</td>
<td>#Html.TextBox("IncrementValue", movie.IncrementValue)</td>
<td><input type="submit" name="Update Movie"/></td>
</tr>
}
}
</table>
Models
public class MovieListModel
{
public MovieListModel()
{
MovieList = new List<MovieModel> {new MovieModel{Id=1,Name = "Apocalypse Now",IncrementValue = 3}, new MovieModel {Id = 2,Name = "Three Lions", IncrementValue = 7} };
}
public List<MovieModel> MovieList { get; set; }
}
public class MovieModel
{
public int Id { get; set; }
public string Name { get; set; }
public int IncrementValue { get; set; }
}
public class IncrementMovieCountModel
{
public int IncrementValue { get; set; }
public int MovieId { get; set; }
}
I'm quite new to MVC4 and my past experience with asp.net has been working with Webforms, so I think I need some careful hand holding. Thank you in advance!
I'm creating a simple web application that is similar to choosing lotto numbers. At the moment the user chooses whichever numbers they want (no limit, starting with "1") and posts it to the next page where I continue to do something with the numbers the user has chosen. The number of numbers - to choose from is dynamic and represented by checkboxes.
The problem is that when the view is submitted, I get the error page "Collection is read-only."
Model - LottoNumbers.cs
bool[] numbers is to store which numbers the user has checked.
namespace Lotto.Models
{
public class LottoNumbers
{
public string name { get; set; }
public bool[] numbers { get; set; }
public LottoNumbers() {
numbers = new bool[49];
}
public LottoNumbers(int limit)
{
numbers = new bool[limit];
}
}
}
in the HomeController.cs ...
I declared LottoNumbers ln, and
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Lotto.Models;
namespace Lotto.Controllers
{
public class HomeController : Controller
{
public LottoNumbers ln;
public ActionResult Index()
{
return View();
}
public ActionResult SetNumbers()
{
ln = new LottoNumbers(30);
// 30 checkboxes will be generated on the view
return View(ln);
}
[HttpPost]
public ViewResult SetNumbers(LottoNumbers l)
{
// get checkbox values
return View();
}
}
}
View - SetNumbers.cshtml
#model Lotto.Models.LottoNumbers
#{
ViewBag.Title = "SetNumbers";
}
<h2>SetNumbers</h2>
#using (Html.BeginForm()) {
<div>
#Html.LabelFor(m => m.name)
#Html.EditorFor(m => m.name)
</div>
<div id="numberlist">
#for (int i = 0; i < Model.numbers.Length; i++)
{
<div class="item-number" style="">
#Html.EditorFor(m => m.numbers[i])
<label class="lbl-number" for="numbers[#i]">#(i+1)</label>
</div>
}
</div>
<div style="clear:both;">
<input type="reset" value="Reset everything" />
<input type="submit" value="Submit" />
</div>
}
Change your array to a list.
public string Name { get; set; }
public List<bool> Numbers { get; set; }
public LottoNumbers()
{
Numbers = new List<bool>();
for (var i = 0; i < 40; i++)
{
Numbers.Add(false);
}
}
public LottoNumbers(int limit)
{
Numbers = new List<bool>();
for (var i = 0; i < limit; i ++)
{
Numbers.Add(false);
}
}
then your view would be
#using (Html.BeginForm())
{
<div>
#Html.LabelFor(m => m.Name)
#Html.EditorFor(m => m.Name)
</div>
<div id="numberlist">
#for (var i = 0; i < Model.Numbers.Count; i++)
{
<div class="item-number" style="">
#Html.CheckBox("Numbers[" + i.ToString() + "]")
<label class="lbl-number" for="numbers[#(i + 1)]">#(i + 1)</label>
</div>
}
</div>
<div style="clear:both;">
<input type="reset" value="Reset everything" />
<input type="submit" value="Submit" />
</div>
}
Null Reference Exception
You are most likely getting the null reference exception after submitting because you are not passing a model back to the view. You set up your page to expect a LottoNumbers model.
[HttpPost]
public ViewResult SetNumbers(LottoNumbers l)
{
// get checkbox values
return View(); //Nothing being passed to the view
}
so after post when your code gets to here
#foreach (var numberBool in Model.Numbers)
it blows up as the Model is null. You can fix this in several ways depending on how you want your app to run. If you want the user to see a blank list of numbers after submit simply do this and add some sort of success message on post back
[HttpPost]
public ViewResult SetNumbers(LottoNumbers l)
{
//process stuff
ln = new LottoNumbers(30);
return View(ln);
}
you can also simply redirect the user back to the index page. Either way the error is caused because you are not passing in a model with numbers back to the view.
If you look at the stack trace of your "Collection is read only" error, you can see that it's happening during Model Binding, which is when it tried to recreate your LottoNumbers object from the data passed back from the form post.
You're getting the error because you're initialising your object with an array size using you're constructor that takes a parameter, but when it posts back the data, it doesn't know what size the array needs to be when it recreates the object.
To solve this, it would be better to use a List instead, and have the size as a property of your LottoNumbers object.
Using a List means you don't have to pre-initialise the array with a size. And having the size as a property means you can embed the size in a hidden input variable, so that it can be passed back to the controller, and it should correctly do the model binding.
I am new to MVC platform and trying a simple application to get hold of the framework,
Application:I am designing an admin application which has a form to enter question and multiple options to the database, I have a viewmodel called "QuestionViewModel" which has properties "Quesiton" and a List of OptionsViewModel in it, "OptionViewModel" consists of "Option" and "IsRightAnswer" properties, so on the UI, I have a QuestionView which displays a textbox for Question and I want a question to have 4 options, so I created a PartialView called "QuestionOptionView" which takes "OptionViewModel" as its model,
now I loop about 4 times through the PartialView and display a TextBox for "Option" and "IsRightAsnwer" radiobutton,
The UI displays TetsBox for "Question" and 4 other "TextBoxes" for entering the "Options",
but when I Post this form, the "Options" does not bind to the ViewModel
How can I achieve Model Binding from these partialview data to the main viewmodel??
public class QuestionViewModel
{
[Display(Name = "Enter a question")]
public string Question { get; set; }
public IList<QuestionOptionViewModel> Options { get; set; }
public int MaxOptions { get { return 4; } }
public QuestionViewModel()
{
Options = new List<QuestionOptionViewModel>();
}
}
public class QuestionOptionViewModel
{
public string Option { get; set; }
public bool IsRightAnswer { get; set; }
}
In the viewm I have as below,
#using (Html.BeginRouteForm("savequestion", new {}, FormMethod.Post)){
<div>
#Html.LabelFor(m => m.Question)
#Html.TextBoxFor(m => m.Question)
</div>
<p>Add options for the question</p>
for (int i = 0; i < Model.MaxOptions; i++)
{
{ Html.RenderPartial("QuestionOption", new Babbi_Test_admin.Models.QuestionOptionViewModel()); }
}
<input type="submit" value="Save" />
When the form is submitted I have my post method as,
[HttpPost]
public void SaveQuestion(QuestionViewModel viewModel)
{
}
My viewModel in the post has "Options" as null
This is happening cause you must be binding the QuestionViewModel with the Action of your Controller instead, you need to create a ViewModel that will have the QuestionViewModel OptionViewModel
Error
[HttpPost]
public void SaveQuestion(QuestionViewModel viewModel)
{
}
In the Post Method you are expecting QuestionViewModel then how can you expect to get the OptionviewModel
Another thing your are Renbdering Partial View for QuestionOptionViewModel which Contains Options and RIght Answer so in the loop you will get the Right Answer 4 thing which is incorrect.
Sample
public class QuestionViewModel
{
//Properties
}
public class OptionViewModel
{
//Properties
}
public class QuestionAndOptionViewmodel
{
// QuestionViewModel and OptionViewModel model object
//Constructors
//Methods
}
[HttpPost]
public void SaveQuestion(QuestionAndOptionViewmodel viewModel)
{
//Your Code Here;
}
Try changing your code to:
for (int i = 0; i < Model.MaxOptions; i++)
{
{ Html.RenderPartial("QuestionOption", Model.Options.Item(i)); }
}
Or even better:
foreach(option in Model.Options)
{
Html.RenderPartial("QuestionOption", option);
}
You will need to initialize the option values in your Question Model constructor
I have a fairly straightforward razor view which needs to be used for a questionnaire, which is used repeatedly until it runs out questions. Checkbox is used for select answers, the problem is when I first submit the form when the next set of questions returned the answers I submit in the first time are retained in the second page. For the simplicity I have reduced the program as follows,
Model,
public class CheckBoxItemDto
{
public int Id { get; set; }
public bool Selected { get; set; }
}
public class CheckBoxModel
{
public CheckBoxModel()
{
Dtos = new List<CheckBoxItemDto>();
}
public IList<CheckBoxItemDto> Dtos { get; set; }
}
Controller,
public class CheckBoxController : Controller
{
public ViewResult Index()
{
CheckBoxModel model = new CheckBoxModel();
for (int i = 0; i < 5; i++)
{
model.Dtos.Add(new CheckBoxItemDto(){Id = i,Selected = true});
}
return View(model);
}
[HttpPost]
public ViewResult Index(CheckBoxModel mdl)
{
CheckBoxModel model = new CheckBoxModel();
for (int i = 5; i < 10; i++)
{
model.Dtos.Add(new CheckBoxItemDto() { Id = i, Selected = i % 2 == 0 });
}
return View(model);
}
}
View,
#model CheckBoxTest.Models.CheckBoxModel
#{
ViewBag.Title = "ViewPage1";
}
<h2>ViewPage1</h2>
#using (Html.BeginForm())
{
for (int i = 0; i < Model.Dtos.Count; i++)
{
#Html.DisplayFor(m => m.Dtos[i].Id)
#Html.DisplayFor(m => m.Dtos[i].Selected)
#Html.CheckBoxFor(m => m.Dtos[i].Selected)
<br/>
}
<input type="submit" value="Index" />
}
In the view after post, DisplayFor and CheckBoxFor column values are different. But It should be the same.
By the sound of it your ModelState is still holding its previous values when you return the view. Try clearing the ModelState in your post action:
ModelState.Clear();
CheckBoxModel model = new CheckBoxModel();
...
In your Index action method that gets run for a POST, ASP.Net MVC is expecting the resulting View to display validation errors. Therefore, the HTML Helper method CheckBoxFor looks in the ModelState before the Model, so it can display the "invalid" value to the user.
If you're not using the View from a POST action to display errors, you can clear the ModelState as Dangerous suggests. Or, you could use the "Post-Redirect-Get" pattern: in your POST action method, display the same View if there are errors, or else redirect to a GET view that displays something else.