One variable in model becomes null after http post - asp.net-mvc

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

Related

MVC BeginCollectionItem

I'm having some issue getting my partial view BeginCollectionItem to save to the database. I have a form which has a dynamic number of "sections" that can be added to the page, and within each of these fields there is a text box where the user can enter the section name.
As far as I can tell the BeginCollectionItem within the partial view is working properly, however I cannot post the info to the database. In my other forms I have used a [bind()] to send the data to the database, is it possible to get this into a list and then post that via a bind?
I've included my code below:
The Model:
namespace project.Models.SetupViewModels
{
public class SOPTopTemplateBuilderViewModel
{
public List<Section> Section { get; set; }
}
public class Section {
public int SectionId { get; set; }
public string SectionText { get; set; }
public string TopTempId { get; set; }
}
}
cshtml:
#model IEnumerable<project.Models.SetupViewModels.Section>
#using (Html.BeginForm("SOPTopTemplateBuilder", "Setup", FormMethod.Post))
{
<div class="main-holder" id="theform">
#foreach(var item in Model)
{
#Html.Partial("_SectionCreator", item)
}
</div>
<button id="add" type="button">Add</button>
<div class="form-group submit-row">
<div class="col-12 float-to-right">
<input type="submit" class="btn btn-default" value="continue" />
</div>
</div>
}
#section Scripts {
<script>
$(document).ready(function () {
var url = '#Url.Action("AddSection")';
var form = $('form');
var recipients = $('#theform');
$('#add').click(function() {
$.post(url, function(response) {
recipients.append(response);
// Reparse the validator for client side validation
form.data('validator', null);
$.validator.unobtrusive.parse(form);
});
});
});
</script>
}
Partial View:
#model project.Models.SetupViewModels.Section
#using HtmlHelpers.BeginCollectionItemCore
#using (Html.BeginCollectionItem("Section"))
{
<div class="new-section">
<div>
<p>New Section</p>
#Html.HiddenFor(m => m.SectionId, new { #class="id" })
#Html.EditorFor(m => m.SectionText, new { #class = "form-control limit-form"})
</div>
</div>
}
Controller:
[HttpPost]
public PartialViewResult AddSection()
{
return PartialView("_SectionCreator", new Section());
}
[HttpGet]
public ActionResult SOPTopTemplateBuilder(){
List<Section> model = new List<Section>();
return View(model);
}
[HttpPost]
public ActionResult SOPTopTemplateBuilder(IEnumerable<Section> soptop)
{
if (ModelState.IsValid)
{}
return View(soptop);
}
Your use of Html.BeginCollectionItem("Section") perpends Section[xxxx] to the name attribute (where xxxx is a Guid) so that you generating inputs with
<input name="Section[xxxx].SectionId" .... />
which posts back to a model containing a collection property named Sections.
Since you already have a model with that property, you can change the POST method to
[HttpPost]
public ActionResult SOPTopTemplateBuilder(SOPTopTemplateBuilderViewModel soptop)
other options include
Using your existing POST method and omitting the "Section" prefix
using Html.BeginCollectionItem("") which will generate
name="[xxxx].SectionId"
Changing the POST method signature to public ActionResult
SOPTopTemplateBuilder(IEnumerable<Section> section) (where the
name of the parameter matches the name of the prefix)
Using a BindAttribute to 'strip' the prefix from the form values
public ActionResult SOPTopTemplateBuilder([Bind(Prefix = "Section")]IEnumerable<Section> soptop)
As a side note, your editing data, so you should always use a view model (say public class SectionViewModel) rather than using data models in your view. - What is ViewModel in MVC?

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

BindingNested List Property to Send to MVC Controller

I have a view where I need to be able to select an item from a dropdown list and add it cumulatively to a list to display. The only way I've been able to think to do this so far is to have a Property which binds to the Dropdownlist, send this back with the cumulative list and have the server add to the List and send it back for display. This works well for the first item because the list to be added to has a Count of 0. Any subsequent additions will cause the controller binding to wipe the List.
Here are the important parts of my code:
Model:
public class CaseAppealViewModel
{
//needs to accumulate records as I add them and retain between server calls
public List<CaseFile> SelectedCases
{
get { return _selectedCases; }
set { _selectedCases = value; }
}
private List<CaseFile> _selectedCases = new List<CaseFile>();
public Nullable<int> ChosenCase { get; set; }
}
View:
#model MySystem.Models.CaseAppealViewModel
#using (Html.BeginForm("AddCase", "AppealLogs", Model))
{
<div class="col-md-6">
#Html.DisplayName("Cases:")
#Html.DropDownListFor(model => model.ChosenCase, CaseAppealViewModel.CaseDropDownList, "", new {#class = "form-control"})
#Html.HiddenFor(model => model.SelectedCases)
#Html.HiddenFor(model => model.Appeals)
</div>
<div class="col-md-6">
<input type="submit" id="AddCase" value="Add"/>
</div>
}
Controller:
public ActionResult AddCase(CaseAppealViewModel cavm)
{
var tempCase = db.CaseFiles.Find(cavm.ChosenCase);
if (tempCase != null)
{
cavm.SelectedCases.Add(db.CaseFiles.Find(cavm.ChosenCase));
}
return View("Details", cavm);
}
Is there a way I can send the SelectedCases List back and forth from the view to the controller without the Count resetting to 0?

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.

How can i maintain objects between postbacks?

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.

Resources