How can i maintain objects between postbacks? - asp.net-mvc

I'm not so experienced using MVC. I'm dealing with this situation. Everything works well until call the HttpPost method where has all its members null. I don't know why is not persisting all the data on it.
And everything works well, because I can see the data in my Html page, only when the user submit the information is when happens this.
[HttpGet]
public ActionResult DoTest()
{
Worksheet w = new Worksheet(..);
return View(w);
}
[HttpPost]
public ActionResult DoTest(Worksheet worksheet)
{
return PartialView("_Problems", worksheet);
}
This is class which I'm using.
public class Worksheet
{
public Worksheet() { }
public Worksheet(string title, List<Problem> problems)
{
this.Title = title;
this.Problems = problems;
}
public Worksheet(IEnumerable<Problem> problems, WorksheetMetadata metadata, ProblemRepositoryHistory history)
{
this.Metadata = metadata;
this.Problems = problems.ToList();
this.History = history;
}
public string Title { get; set; }
public List<Problem> Problems { get; set; } // Problem is an abstract class
public WorksheetMetadata Metadata { get; set; }
public ProblemRepositoryHistory History { get; set; }
}
And my razor view.... the razor view shows successfully my view. I realized something rare, please note in my 5 and 6 lines that I have HiddenFor method, well if I used that, when calls HTTPPOST persists the data, I don't know why.
#model Contoso.ExercisesLibrary.Core.Worksheet
<div id="problemList">
<h2>#Html.DisplayFor(model => model.Metadata.ExerciseName)</h2>
#Html.HiddenFor(model => model.Metadata.ExerciseName)
#Html.HiddenFor(model => model.Metadata.ObjectiveFullName)
#for (int i = 0; i < Model.Problems.Count; i++)
{
<div>
#Html.Partial(Contoso.ExercisesLibrary.ExerciseMap.GetProblemView(Model.Problems[i]), Model.Problems[i])
</div>
}
</div>
UPDATE
I'm using a static class to get the view name, but as I'm testing I'm just using this Partial view
#model Contoso.ExercisesLibrary.AbsoluteArithmetic.Problem1
<div>
<span style="padding:3px; font-size:18px;">#Model.Number1</span>
<span style="padding:5px; font-size:18px;">+</span>
<span style="padding:5px; font-size:18px;">#Model.Number2</span>
<span style="padding:5px; font-size:18px;">=</span>
<span style="font-size:18px">
#Html.EditorFor(model => model.Result, new { style = "width:60px; font-size:18px;" })
#Html.ValidationMessageFor(model => model.Result)
</span>
</div>
#section Scripts {
}
And here the user do the post
#model Contoso.ExercisesLibrary.Core.Worksheet
<form method="post">
#Html.Partial("_Problems", Model)
<input type="submit" value="Continue" />
</form>

The Model Binder will 'bind' or link input fields on your view to the model. It will not bind display fields (like label), that is why you need the HiddenFor it will add an <input type="hidden" which will then be bound to the Model when you Post.

You can use 'TempData'. It is used to pass data from current request to subsequent request means incase of redirection.
This link also helps you.
TempData
SO Tempdata

Make sure your form tag looks like the following, for instance the controller name, action method, the form method and an id for the form. I am referring to the #using statement. In my case the controller name is RunLogEntry, the action method is Create and the id is form.
Normal Post from View to Controller
#using (Html.BeginForm("Create", "RunLogEntry", FormMethod.Post, new { id = "form", enctype = "multipart/form-data" }))
{
<div id="main">
#Html.Partial("_RunLogEntryPartialView", Model)
</div>
}
If you want to post via Jquery, could do the following:
$.post("/RunLogEntry/LogFileConfirmation",
$("#form").serialize(),
function (data) {
//this is the success event
//do anything here you like
}, "html");

You must specify a form with correct attribute in your view to perform post action
<form action="Test/DoTest" method="post">
...
</form>
or
#using(Html.BeginForm("DoTest", "Test", FormMethod.Post)) {
...
}
The second is recommended.

Put your entire HTML code under:
#using(Html.BeginForm())
tag.

Related

Why my MVC view passes null but not the value it has? [duplicate]

This question already has an answer here:
Html.DisplayFor not posting values to controller in ASP.NET MVC 3
(1 answer)
Closed 5 years ago.
View shows proper value (OrderId = 3):
// GET:
public ActionResult ConfirmOrder()
{
//simplified code here
var model = new ConfirmOrderViewModel()
{
OrderId = 3,
};
return View(model);
}
View works fine one way (value visible on the screen) Html-part below:
#model Test.Models.Views.ConfirmOrderViewModel
#{
ViewBag.Title = "My title";
}
<h2>#ViewBag.Title</h2>
#using (Html.BeginForm("ConfirmOrder", "Client", FormMethod.Post, new {
#class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
<div class="row" style="padding:10px; margin:15px">
<div>
<div class="col-sm-3">
#Html.DisplayFor(m => m.OrderId)
</div>
</div>
</div>
}
ConfirmOrderViewModel class looks like this:
public class ConfirmOrderViewModel
{
public int OrderId { get; set; }
}
4. But when it comes to post it back, only null I'm having:
// POST:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ConfirmOrder(ConfirmOrderViewModel ViewModel)
{
//at this moment: ViewModel.OrderId = null
return RedirectToAction("Index");
}
Controller Name is ok, Methods works... no errors. Just null after clicking the OK button on page. What can cause bad model binding here?
The DisplayFor helper method will just render the value of the OrderId property. As the name suggests, It is more for displaying to the user. If you want the value for OrderId to be posted to the http post action method, you need to keep that in in a form field inside the form.
You can keep in a hidden field inside the form
#using (Html.BeginForm("ConfirmOrder", "Client"))
{
#Html.DisplayFor(m => m.OrderId)
#Html.HiddenFor(a=>a.OrderId)
<input type="submit" value="Confirm" />
}

edit form not working, data is being passed to server but not the httpPost method

I had asked question yesterday, which can be found here and
based upon the answer I got from asp.net/mvc forum which can be found here, I was told to clear my modelstate, as by default my form tends to hold its default value, and not the value I just tried to update. So, I added Modelstate.Clear(), which still doesn't work. Can anyone tell me if i'm using the ModelState.Clear() in a wrong place or if I have to change something?
So, here is the problem, I have a edit form which shows its current values in textboxes, when user clicks edit button. If a user wants to edit some current value which is shown in textbox he edits the value in text box and clicks the save changes button. What currently is happening is in my HttpPost method when i check the values that are being passed, I don't get the new value user just provided, rather I get the value that was shown as current value in form.
But when I check in the developer tools in chrome, it shows the new value user just provided as the value that is being passed to server.
Here is my view
#using BootstrapSupport
#model AdminPortal.Areas.Hardware.Models.EditModule
#{
ViewBag.Title = "Edit";
Layout = "~/Views/shared/_BootstrapLayout.basic.cshtml";
}
<fieldset>
<legend>Module <small>Edit</small></legend>
#using (Html.BeginForm("Edit", "Module"))
{
#Html.ValidationSummary(true)
#Html.HiddenFor(m=>m.Id)
for(var i = 0; i < Model.Properties.Count(); i++)
{
#Html.HiddenFor(model=>model.HiddenProperties[i].Name)
#Html.HiddenFor(model=>model.HiddenProperties[i].Value)
<label class="label">#Model.Properties[i].Name</label>
<div class="input-block-level">#Html.TextBoxFor(model => model.Properties[i].Value)</div>
}
<div class="form-actions">
<button type="submit" class="btn btn-primary" id="Submit">Save changes</button>
#Html.ActionLink("Cancel", "ModuleList", null, new { #class = "btn " })
</div>
}
</fieldset>
<p>
#Html.ActionLink("Back to List", "ModuleList")
</p>
Here is the get and post method in controller
[HttpGet]
public ActionResult Edit(long id)
{
var module = _repository.GetModuleProperties(id);
ModelState.Clear();
return View(module);
}
[HttpPost]
public ActionResult Edit(EditModule module)
{
ModelState.Clear();
if (ModelState.IsValid)
{
_repository.SaveModuleEdits(module);
Information("Module was successfully edited!");
return RedirectToAction("ModuleList", "Module", new {area = "Hardware"});
}
Error("Edit was unsuccessful, if the problem persists please contact Merijn!");
return RedirectToAction("ModuleList", "Module", new { area = "Hardware" });
}
The problem is with your model:
public class EditModule
{
public long Id { get; set; }
public List<PropertyViewModel> Properties { get; set; }
public List<PropertyViewModel> HiddenProperties
{
get { return Properties; }
set { Properties = value; }
}
}
You're posting back both Properties and HiddenProperties, but only changing Properties. The modelbinder sets the new values in Properties and then sets the values for HiddenProperties which in turn sets Properties and you've just overwritten your changes.
I'm not sure what exactly you're trying to do with HiddenProperties, but it's completely broken as it's currently set up.
UPDATE: Suggested changes
Model
public class EditModule
{
public long Id { get; set; }
public List<PropertyViewModel> Properties { get; set; }
}
Removed HiddenProperties property
Controller Action
[HttpPost]
public ActionResult Edit(long id, EditModule module)
{
var originalModule = _repository.GetModuleProperties(id);
// do whatever comparisons you want here with originalModule.Properties / module.Properties
if (ModelState.IsValid)
{
_repository.SaveModuleEdits(module);
Information("Module was successfully edited!");
return RedirectToAction("ModuleList", "Module", new {area = "Hardware"});
}
Error("Edit was unsuccessful, if the problem persists please contact Merijn!");
return RedirectToAction("ModuleList", "Module", new { area = "Hardware" });
}
Edit POST version takes the id just like the GET version. You use this id to lookup the original version of the module from the database and then you can compare original and posted versions of Properties.
View
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
#Html.HiddenFor(m=>m.Id)
for(var i = 0; i < Model.Properties.Count(); i++)
{
<label class="label">#Model.Properties[i].Name</label>
<div class="input-block-level">#Html.TextBoxFor(model => model.Properties[i].Value)</div>
}
<div class="form-actions">
<button type="submit" class="btn btn-primary" id="Submit">Save changes</button>
#Html.ActionLink("Cancel", "ModuleList", null, new { #class = "btn " })
</div>
}
The Html.BeginForm() syntax tells Razor to just use the current page's URL as the action for the form. HiddenProperties form fields have been removed.

One variable in model becomes null after http post

I'm having a problem with my model losing one of its values when submitted in a http post form.
The controller methods:
[HttpGet]
public ActionResult AddTeam(int id)
{
return PartialView("_AddTeam", ViewModelGenerator.TeamFormModel(id));
}
[HttpPost]
public ActionResult AddTeam(TeamFormModel model)
{
TournamentTeamService.CreateTeam(model);
return RedirectToAction("Index", "Tournament", null);
}
The form model:
public class TeamFormModel
{
public TeamViewModel Team { get; set; }
public TournamentViewModel Tournament { get; set; }
public List<PlayerViewModel> Players { get; set; }
}
This function is called from the controller, it creates a new instance of the form model and gets the Tournament from the database and puts it into a view model
public static TeamFormModel TeamFormModel(int id)
{
var _db = new DbContext();
var model = new TeamFormModel();
var tempModel = new TournamentViewModel();
var temp = (from t in _db.tournament
where t.Id == id
select t).SingleOrDefault();
tempModel.Id = temp.Id;
tempModel.Name = temp.Name;
tempModel.SignupStartDate = temp.SignupStartDate;
tempModel.SignupEndDate = temp.SignupEndDate;
tempModel.StartDate = temp.StartDate;
tempModel.EndDate = temp.EndDate;
tempModel.Description = temp.description;
tempModel.TournamentType = temp.TournamentType;
model.Players = new List<PlayerViewModel>();
model.Tournament = tempModel;
return model;
}
Here's the view, the code with the inputs for the player list is missing, it's because it is appendedTo the player-forms div via jquery and it works perfectly so i didn't consider it relevant in this case.
#model Context.ViewModels.TeamFormModel
<script src="~/Scripts/jquery-1.8.2.min.js"></script>
<script src="~/Scripts/jquery.validate.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
#using (Html.BeginForm("AddTeam", "Tournament", FormMethod.Post, new { enctype = "multipart/form-data", #class = "form-horizontal" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<h2>Registering a team to #Model.Tournament.Name </h2>
<div class="control-group">
#Html.LabelFor(model => model.Team.Name)
<div class="control">
#Html.EditorFor(model => model.Team.Name)
</div>
<a class="add-player-form" href="javascript:void(0)">Add a player</a>
<div id="player-forms">
</div>
</div>
<div class="control-group">
<div class="control" style="clear: both;">
<input type="submit" value="Register" />
</div>
</div>
}
Before i submit this form, the TeamFormModel is perfectly set up, all values are inserted and i only need to save everything to DB. However when it goes back to the controller, the tournament variable in TeamFormModel is null. So when i send it to a function that changes it to a Db model and submits it to db fails everytime.
I know there are ways around the problem like only keeping the id of the tournament instead of the whole model and then get it from db after the form is submitted but it really bugs me that it behaves this way.
I wasn't able to find anything on this specific issue here on stackoverflow, there were a few similar questions but nothing that presented it self the same way.
you have to set something like this
#Html.HiddenFor(x => x.TournmentID);
something has to retain the value of the model that was passed into the view while the view is rendered, this will pass the value back to the controller on post

ASP.NET MVC, passing Model from View to Controller

I'm having trouble with ASP.NET MVC and passing data from View to Controller. I have a model like this:
public class InputModel {
public List<Process> axProc { get; set; }
public string ToJson() {
return new JavaScriptSerializer().Serialize(this);
}
}
public class Process {
public string name { get; set; }
public string value { get; set; }
}
I create this InputModel in my Controller and pass it to the View:
public ActionResult Input() {
if (Session["InputModel"] == null)
Session["InputModel"] = loadInputModel();
return View(Session["InputModel"]);
}
In my Input.cshtml file I then have some code to generate the input form:
#model PROJ.Models.InputModel
#using(Html.BeginForm()) {
foreach(PROJ.Models.Process p in Model.axProc){
<input type="text" />
#* #Html.TextBoxFor(?? => p.value) *#
}
<input type="submit" value="SEND" />
}
Now when I click on the submit button, I want to work with the data that was put into the textfields.
QUESTION 1: I have seen this #Html.TextBoxFor(), but I don't really get this "stuff => otherstuff". I concluded that the "otherstuff" should be the field where I want to have my data written to, in this case it would probably be "p.value". But what is the "stuff" thing in front of the arrow?
Back in the Controller I then have a function for the POST with some debug:
[HttpPost]
public ActionResult Input(InputModel m) {
DEBUG(m.ToJson());
DEBUG("COUNT: " + m.axProc.Count);
return View(m);
}
Here the Debug only shows something like:
{"axProc":[]}
COUNT: 0
So the returned Model I get is empty.
QUESTION 2: Am I doing something fundamentally wrong with this #using(Html.BeginForm())? Is this not the correct choice here? If so, how do I get my model filled with data back to the controller?
(I cannot use "#model List< Process >" here (because the example above is abbreviated, in the actual code there would be more stuff).)
I hope someone can fill me in with some of the details I'm overlooking.
Change your view to some thing like this to properly bind the list on form submission.
#using(Html.BeginForm()) {
for(int i=0;i<Model.axProc.Count;i++){
<span>
#Html.TextBoxFor(model => model.axProc[i].value)
</span>
}
<input type="submit" value="SEND" />
}
In #Html.TextBoxFor(stuff => otherstuff) stuff is your View's model, otherstuff is your model's public member.
Since in the View you want to render input elements for the model member of a collection type (List), you should first create a separate partial view for rendering a single item of that collection (Process). It would look something like this (name it Process.cshtml, for example, and place into the /Views/Shared folder):
#model List<PROJ.Models.Process>
#Html.TextBoxFor(model => p.value)
Then, your main View would look like this:
#model PROJ.Models.InputModel
#using(Html.BeginForm()) {
foreach(PROJ.Models.Process p in Model.axProc){
#Html.Partial("Process", p)
}
<input type="submit" value="SEND" />
}
Also, check that the loadInputModel() method actually returns something, e.g. not an empty list.

ASP.NET MVC Paging for a search form

I've read several different posts on paging w/ in MVC but none describe a scenario where I have something like a search form and then want to display the results of the search criteria (with paging) beneath the form once the user clicks submit.
My problem is that, the paging solution I'm using will create <a href="..."> links that will pass the desired page like so: http://mysite.com/search/2/ and while that's all fine and dandy, I don't have the results of the query being sent to the db in memory or anything so I need to query the DB again.
If the results are handled by the POST controller action for /Search and the first page of the data is rendered as such, how do I get the same results (based on the form criteria specified by the user) when the user clicks to move to page 2?
Some javascript voodoo? Leverage Session State? Make my GET controller action have the same variables expected by the search criteria (but optional), when the GET action is called, instantiate a FormCollection instance, populate it and pass it to the POST action method (there-by satisfying DRY)?
Can someone point me in the right direction for this scenario or provide examples that have been implemented in the past? Thanks!
My method is to have an Action that handles both the post and the get scenarios.
This is my which can be handled by both GET and POST methods:
public ViewResult Index([DefaultValue(1)] int page,
[DefaultValue(30)] int pageSize,
string search,
[DefaultValue(0)] int regionId,
[DefaultValue(0)] int eventTypeId,
DateTime? from,
DateTime? to)
{
var events = EventRepo.GetFilteredEvents(page, pageSize, search, regionId, eventTypeId, from, to);
var eventFilterForm = EventService.GetEventFilterForm(from, to);
var eventIndexModel = new EventIndexModel(events, eventFilterForm);
return View("Index", eventIndexModel);
}
The eventFilterForm is a presentation model that contains some IEnumerable<SelectListItem> properties for my search form.
The eventIndexModel is a presentation model that combines the eventFilterForm and the results of the search - events
The events is a special type of IPagedList. You can get more information and code for that here and here. The first link talks about IPagedList where as the second link has an Advanced Paging scenario which you should need.
The advanced paging has the following method that I use:
public static string Pager(this HtmlHelper htmlHelper, int pageSize, int currentPage, int totalItemCount, RouteValueDictionary valuesDictionary)
And I use it like so:
<%= Html.Pager(Model.Events.PageSize,
Model.Events.PageNumber,
Model.Events.TotalItemCount,
new
{
action = "index",
controller = "search",
search = ViewData.EvalWithModelState("Search"),
regionId = ViewData.EvalWithModelState("RegionId"),
eventTypeId = ViewData.EvalWithModelState("EventTypeId"),
from = ViewData.EvalDateWithModelState("From"),
to = ViewData.EvalDateWithModelState("To")
}) %>
This creates links that look like:
/event/search?regionId=4&eventTypeId=39&from=2009/09/01&to=2010/08/31&page=3
HTHs,
Charles
Ps. EvalWithModelState is below:
PPs. If you are going to put dates into get variables - I would recommend reading my blog post on it... :-)
/// <summary>
/// Will get the specified key from ViewData. It will first look in ModelState
/// and if it's not found in there, it'll call ViewData.Eval(string key)
/// </summary>
/// <param name="viewData">ViewDataDictionary object</param>
/// <param name="key">Key to search the dictionary</param>
/// <returns>Value in ModelState if it finds one or calls ViewData.Eval()</returns>
public static string EvalWithModelState(this ViewDataDictionary viewData, string key)
{
if (viewData.ModelState.ContainsKey(key))
return viewData.ModelState[key].Value.AttemptedValue;
return (viewData.Eval(key) != null) ? viewData.Eval(key).ToString() : string.Empty;
}
Make the Search parameter part of your View Model:
public SearchViewModel
{
string SearchParameters { get; set; }
List<SearchObjects> SearchResults { get;set; }
}
Then just set the Search Textbox equal to SearchParameters.
You cannot "store" the search query unless you bring back ALL results and then store those in the page somehow. That is horribly inefficient. The web is stateless, so you will have to go back to the database and re-query for more results.
I understand what you are saying; you could change the form to use buttons and post the page back everytime. Or, you could pass all the criteria in the URL for the paging as querystring variables. Or you could use JQuery to do the post (it has a $.post method that can be invoked from a link click or other click (http://api.jquery.com/jQuery.post/).
HTH.
This problem goes away if you include the search text, as well as the current results page, in your querystring instead of POSTing the search text. As an added benefit, your users can then bookmark their search results.
To do this your search button just needs to build the GET request URL using the current value of the search box. This can be done either in javascript or by using GET as your search form's method attribute, e.g. <form method="get" action="/search">.
I recommend cacheing your search results and giving them an ID. Then for each paging link, you can reference the search ID as a parameter (on each search page link) and in your action, pull it from cache, then query over it.
Using this method, you don't need to worry about anything other than the first POST submit of the search form.
Refer to my post for more details.
I had this same problem and here's what I did.
Download PagedList from Nuget
Change your form to do a GET and create a ViewModel type similiar to this (if you love AdventureWorks and Model Binding as much as I do):
`
using PagedList;
namespace SearchFormResultPagingExample.Models {
public class SearchViewModel {
public int? Page { get; set; }
public string EmailAddress { get; set; }
public string LastName { get; set; }
public IPagedList<Contact> SearchResults { get; set; }
public string SearchButton { get; set; }
}
}
`
3.Use the ViewModel as the parameter to your controller's action method
using System.Linq;
using System.Web.Mvc;
using SearchFormResultPagingExample.Models;
using PagedList; //NOTE: use Nuget to reference PagedList
namespace SearchFormResultPagingExample.Controllers {
public class SearchController : Controller {
const int RecordsPerPage = 25;
public ActionResult Index(SearchViewModel model) {
if (!string.IsNullOrEmpty(model.SearchButton) || model.Page.HasValue) {
var entities = new AdventureWorksEntities();
var results = entities.Contacts.Where(c => c.LastName.StartsWith(model.LastName) && c.EmailAddress.StartsWith(model.EmailAddress))
.OrderBy(o => o.LastName);
var pageIndex = model.Page ?? 0;
model.SearchResults = results.ToPagedList(pageIndex, 25);
}
return View(model);
}
}
}
Use the pager on in your View:
#model SearchFormResultPagingExample.Models.SearchViewModel
#using PagedList.Mvc;
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm("Index", "Search", FormMethod.Get)) {
#Html.ValidationSummary(false)
<fieldset>
<legend>Contact Search</legend>
<div class="editor-label">
#Html.LabelFor(model => model.EmailAddress)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.EmailAddress)
#Html.ValidationMessageFor(model => model.EmailAddress)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.LastName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.LastName)
#Html.ValidationMessageFor(model => model.LastName)
</div>
<p>
<input name="SearchButton" type="submit" value="Search" />
</p>
</fieldset>
}
#if (Model.SearchResults != null && Model.SearchResults.Count > 0) {
foreach (var result in Model.SearchResults) {
<hr />
<table width="100%">
<tr>
<td valign="top" width="*">
<div style="font-weight: bold; font-size:large;">#result.LastName, #result.FirstName</div>
#result.Title<br />
#result.Phone<br />
#result.EmailAddress
</td>
</tr>
</table>
}
<hr />
#Html.PagedListPager(Model.SearchResults,
page => Url.Action("Index", new RouteValueDictionary() {
{ "Page", page },
{ "EmailAddress", Model.EmailAddress },
{ "LastName", Model.LastName }
}),
PagedListRenderOptions.PageNumbersOnly)
}
MVC will coerce the querystring to and from your ViewModel type parameter. It's very slick!

Resources