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
Related
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.
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 have a ParentModel which contains property 'Childs' (It is a list of ChildModel) and I have strong typed partial view with List of ChildModel as model. What I want to do is when button ADD is clicked, my controller will populate 'Childs' property with ChildModel but I have a problem because the model always went back to nothing after POST.
The first time I clicked the ADD button, the program Add new ChildModel to the 'Childs' property, but the 2nd time I click the ADD button, the 'Childs' property went back to nothing.
This is my code:
Model :
public class ParentModel
{
private List<ChildModel> _childs;
public List<ChildModel> Childs {
get { return _childs; }
set { _childs = value; }
}
public ParentModel()
{
_childs = new List<ChildModel>();
}
}
Controller :
public ActionResult Add()
{
ParentModel model = new ParentModel();
return View(model);
}
[HttpPost()]
public ActionResult Add(ParentModel model, string submit)
{
if (submit == "addChild") {
model.Childs.Add(new ChildModel());
}
else {
//SAVE
}
return View(model);
}
PartialView:
#model List<KlikRX.ChildModel>
//do something with model
<button type="submit" name="submit" value="addChild">ADD</button>
MainView:
#Html.Partial("PartialView", model.Childs)
Any help will be appreciated, sorry for bad english.
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.