Why is my ViewData list null? MVC 4 - asp.net-mvc

I have two models, question and answer. I want to insert a list of answers thru ViewModel to a question but it seems in my post method my list is getting null. That might be a bad implementation as well, because I am returning a model of my question back when I post something and I guess my List is just getting null. How could I fix this?
Edit: I remade the controller and the view based on comments you gave me: Thats how it looks now, but seems my Answer List to be Empty again.
ViewModel:
public class ViewModel
{
public IEnumerable<Answer> Answers { get; set; }
public Question Question { get; set; }
}
Controller:
[Authorize]
public ActionResult Create()
{
ViewModel vm = new ViewModel();
ViewBag.BelongToTest = new SelectList(db.Tests, "TestId" , "TestTitle").FirstOrDefault();
vm.Question = new Question { Question_Text = String.Empty };
vm.Answers = new List<Answer> { new Answer { CorrectOrNot = false, AnswerText = "", OpenAnswerText = "" } };
return View(vm);
}
//
// POST: /Question/Create
[HttpPost]
[Authorize]
public ActionResult Create(ViewModel vm)
{
if (ModelState.IsValid)
{
vm.Question.BelongToTest = (from t in db.Tests
join m in db.Members on t.AddedByUser equals m.MemberId
where m.UserID == WebSecurity.CurrentUserId &&
t.AddedByUser == m.MemberId
orderby t.TestId descending
select t.TestId).FirstOrDefault();
db.Questions.Add(vm.Question);
db.SaveChanges();
if (vm.Answers != null)
{
foreach (var i in vm.Answers)
{
i.BelongToQuestion = vm.Question.QuestionId;
db.Answers.Add(i);
}
}
db.SaveChanges();
ViewBag.Message = "Data successfully saved!";
ModelState.Clear();
}
ViewBag.BelongToTest = new SelectList(db.Tests, "TestId", "TestTitle", vm.Question.BelongToTest);
vm.Question = new Question { Question_Text = String.Empty };
vm.Answers = new List<Answer> { new Answer { CorrectOrNot = false, AnswerText = "", OpenAnswerText = "" } };
return View("Create" , vm);
}
View:
#model MvcTestApplication.Models.ViewModel
#using MvcTestApplication.Models
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
#{
ViewBag.Title = "Create";
}
#using (Html.BeginForm("Create", "Question", FormMethod.Post)) {
<h2>Create</h2>
<table>
<tr>
<th>Question Name</th>
</tr>
<tr>
<td>#Html.EditorFor(model=>model.Question.Question_Text)</td>
</tr>
</table>
<table id="dataTable">
<tr>
<th>Correct?</th>
<th>Answer text</th>
<th>Open Answer</th>
</tr>
#foreach(var i in Model.Answers)
{
<tr>
<td>#Html.CheckBoxFor(model=>i.CorrectOrNot)</td>
<td>#Html.EditorFor(model=>i.AnswerText)</td>
<td>#Html.EditorFor(model=>i.OpenAnswerText)</td>
</tr>
}
</table>
<input type="button" id="addNew" value="Add Answer"/>
<input type="submit" value="Create" />
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
<script lang="javascript">
$(document).ready(function () {
//1. Add new row
$("#addNew").click(function (e) {
e.preventDefault();
var $tableBody = $("#dataTable");
var $trLast = $tableBody.find("tr:last");
var $trNew = $trLast.clone();
var suffix = $trNew.find(':input:first').attr('name').match(/\d+/);
$trNew.find("td:last").html('Remove');
$.each($trNew.find(':input'), function (i, val) {
// Replaced Name
var oldN = $(this).attr('name');
var newN = oldN.replace('[' + suffix + ']', '[' + (parseInt(suffix) + 1) + ']');
$(this).attr('name', newN);
//Replaced value
var type = $(this).attr('type');
if (type.toLowerCase() == "text") {
$(this).attr('value', '');
}
// If you have another Type then replace with default value
$(this).removeClass("input-validation-error");
});
$trLast.after($trNew);
// Re-assign Validation
var form = $("form")
.removeData("validator")
.removeData("unobtrusiveValidation");
$.validator.unobtrusive.parse(form);
});
// 2. Remove
$('a.remove').live("click", function (e) {
e.preventDefault();
$(this).parent().parent().remove();
});
});
</script>
}

For the ModelBinder to bind to a List the HTML form must be sequentially indexed.
Your
<td>#Html.CheckBoxFor(model=>a.CorrectOrNot)</td>
<td>#Html.EditorFor(model=>a.AnswerText)</td>
<td>#Html.EditorFor(model=>a.OpenAnswerText)</td>
is creating something that will be bound to an individual answer. You need to render HTML that will be bound to a List, something like
#for (int i = 0; i < ((List<Answer>)ViewData["Answers"]).Count; i++)
{
<tr>
<td>#Html.CheckBoxFor(model=>((List<Answer>)ViewData["Answers"])[i].CorrectOrNot)</td>
<td>#Html.EditorFor(model=>((List<Answer>)ViewData["Answers"])[i].AnswerText)</td>
<td>#Html.EditorFor(model=>((List<Answer>)ViewData["Answers"])[i].OpenAnswerText)</td>
</tr>
}
Also, this looks pretty awful casting ViewData all over the place. It would generally be better, if you plan to keep this approach creating a real view model. You could pass that model to the view and it could wrapper both question and answer collections.
EDIT:
You still need to have a sequential index against your list which your edited implementation is not supplying. Something like
#for (int i = 0; i < Model.Answers.Count; i++)
{
<tr>
<td>#Html.CheckBoxFor(model=> Model.Answers[i].CorrectOrNot)</td>
<td>#Html.EditorFor(model=> Model.Answers[i].AnswerText)</td>
<td>#Html.EditorFor(model=> Model.Answers[i].OpenAnswerText)</td>
</tr>
}

ViewData is relevant when going from the controller to the view. It won't post back.
You should relay on the (model / parameter) binding that will take care of passing List<Answer> answerList for you

ViewData is only to transfer the data between the view and controller. You can use session to transfer the data between the controller

Thanks for the comments. They really helped me out. It was all correct that you say but there was something that was missing. My IEnumerable in the ViewModel simply does not allow me to index my values, instead using IList helped me out to index everything as it is supposed to be and everything works.

Related

View is returning FormCollection in Chrome but not IE

I have a View that has a DDL. Once a value is selected in the DDL the form can be submitted and the items for the vendor will be retrieved from a Web Api service and the page is reloaded. This form works as designed in Chrome but it fails to find the DDL value in the FormCollection object in IE. What did I miss?
View Code
#using AMCWeb.Models
#model AppointsViewModel
#{
ViewBag.Title = "Index";
}
<h2>Appraisal Appointment</h2>
#using (Html.BeginForm("GetAppointmentsByVendor", "AppraisalAppointment", FormMethod.Post, new {#id = "validationlist"}))
{
<br/>
<br/>
#Html.DropDownListFor(model => model.SelectedCompany, new SelectList(Model.Vendors.OrderBy(s => s.Company), "Id", "Company", Model.SelectedCompany), "- Select Vendor -")
<br/>
<input type="submit" value="Get Appointments" onclick="location.href='#Url.Action("GetAppointmentsByVendor", "AppraisalAppointment")'" />
<br/>
<table>
<tr>
<th>Id</th>
<th>Loan Number</th>
<th>State</th>
<th>Order Date</th>
<th>Apt Date</th>
<th>Est Due Date</th>
<th>Fees</th>
<th>MN Status</th>
</tr>
#foreach (AMCWeb.Models.AppraisalAppointment appraisalAppointment in Model.Appraisals)
{
//...load the table with data
}
</table>
}
Controller Code
public ActionResult GetAppointmentsByVendor(FormCollection formValues)
{
List<string> messages = new List<string>();
if (formValues["selectedCompany"].Trim() == string.Empty)
{
messages.Add("You must select a Vendor to begin.");
ViewBag.Messages = messages;
appointments.Vendors = _vendorRepository.Get();
return View("Index", appointments);
}
var vendorId = Convert.ToInt32(formValues["selectedCompany"]);
appointments.Appraisals = _appraisalAppointmentRepository.GetByVendor(vendorId);
appointments.Vendors = _vendorRepository.Get();
appointments.SelectedCompany = vendorId;
return View("Index", appointments);
}
Value from IE:
Value from Chrome:
UPDATE:
It appears that the vendorId was being passed. What I found was happening as I stepped through the code was it was indeed not in the FormCollection object, the code then broke on the first 'if statement' because it was not in the collection but if I continued to step through the code it jumped right back up the the first line var vendorId = Convert.ToInt32(formValues["selectedCompany"]); and the value was there. So I modified the code as follows
public ActionResult GetAppointmentsByVendor(FormCollection formValues)
{
// to set up the model for reload
IEnumerable<AppraisalAppointment> appointment = new[] { new AppraisalAppointment() };
appointments.Appraisals = appointment;
appointments.Vendors = _vendorRepository.Get();
// for use in the tryparse
var selectedCompany = formValues["selectedCompany"];
int vendorId;
// for message deliver to the user
List<string> messages = new List<string>();
// if the vendorId is not an int then they didn't select one return
if (!Int32.TryParse(selectedCompany, out vendorId))
{
appointments.SelectedCompany = 0;
messages.Add("You must select a Vendor to begin.");
ViewBag.Messages = messages;
return View("Index", appointments);
}
// get the data for the user
appointments.Appraisals = _appraisalAppointmentRepository.GetByVendor(vendorId);
appointments.SelectedCompany = vendorId;
return View("Index", appointments);
}

send a ViewModel which contains a list with a Html.BeginForm (MVC 4)

My viewmodel contains a integer list, the problem I have is that when I send my modified form viewmodel, it is always equal to null.
My ViewModel :
public class testViewModel
{
public List<int> itemTest { get; set;
}
Action in my Controller :
For example, I'll try to sum ​​the new values ​​entered into the form, but the sum calculated is always equal to 0, nothing changes.
public ActionResult form(int nbre)
{
testViewModel montest = new testViewModel()
{
itemTest = new List<int>()
};
for(int i=0;i<nbre ;i++)
{
montest.itemTest.Add(0);
}
return View(montest);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult form(testViewModel maListe)
{
int somme = 0;
if (maListe.itemTest != null)
{
if (maListe.itemTest.Count() != 0)
{
foreach (var item in maListe.itemTest)
{
somme += item;
}
}
}
//listtest = maListe;
return RedirectToAction("test2", new { qte = somme });
}
My view
#model MvcWebRole1.ViewModels.testViewModel
#{
ViewBag.Title = "Formulaire";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<table>
#foreach (var item in Model.itemTest)
{
<tr >
<td >
#Html.Label("Quantitée")
</td>
<td>
#Html.EditorFor(model => item)
#Html.ValidationMessageFor(model => item)
</td>
</tr>
}
</table>
<input type="submit" value="Valider" />
}
Thank you kindly help me
You need to index each item in your collection. The issue with your code seems to be the use of foreach. You really want to use for instead and pass in the index with the EditorFor call.
for (int i = 0; i < Model.Items.Count; i++) {
#Html.EditorFor(m => m.Items[i])
}
This only works for ordered lists that will never change their order. If you want to reorder items I suggest your read Phil Haack's great post on sending lists to the server.
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
list binding
<form>
#for(int i=0;i<Model.itemTest.Count ;i++)
{
#Html.TextBoxFor(x=>x.itemTest[i])
//or just <input type="text" name="itemTest "/> working to
}
for(int i=0;i<nbre ;i++)
{
montest.itemTest.Add(0);
}
return View(montest);
Looks like you are filling your int array with zeroes instead of i's. This should read montest.itemTest.Add(i); .

I cannot get just the selected values from dropdownlists in my view back in my controller

I hava a view where I have a list of links, being each link a region where the companies has offices.
Everytime I select a region, I get a list of processes. For every process, I get a dropdowlist from where to choose a owner of the process and a list of checkboxs of tests to choose.
In my controller, I get string[] OwnerId as the values selected in the dropdowlists.
The thing is, I get all values from all dropdowlists, not just those that were selected. How can I get just the ones I selected??
This is my view
#using CTTModel
#using TestingTool.ViewModels
#model TestRunModel
#{
ViewBag.Title = "Create";
}
<h2>
Create</h2>
<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())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Regions</legend>
#foreach (Region region in Model.Regions)
{
#Html.ActionLink(#region.Name, "Create", new { id = region.Id })<br />
}
<div class="editor-field">
#foreach (ProcessModel process in Model.Processes)
{
<h1>#process.Name</h1>
**List<User> users = ViewBag.Users;
<select id="OwnerId" name="OwnerId" >
#foreach (User user in users)
{
<option value="#user.Id">#user.Name</option>
}
</select>**
<table>
<tr>
#{
int cnt = 0;
foreach (TestModel testModel in process.Tests)
{
if (cnt++ % 3 == 0)
{
#: </tr> <tr>
}
#: <td>
<input type="checkbox"
name="selectedTests"
value="#testModel.Id/#testModel.ProcessId/#testModel.RegionId"
#(Html.Raw(testModel.Active ? "checked=\"checked\"" : "")) />
#testModel.Name #:: #testModel.Description
#:</td>
}
#: </tr>
}
</table>
}
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
<fieldset>
<legend>Test Screen</legend>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
And this is my controller. The Create Post does nothing yet, I'm trying to get the right values first.
//
// GET: /TestPreparation/Create
public ActionResult Create(int id = 1)
{
TestRunModel testRunModel = new TestRunModel();
foreach (Region region in _db.Regions)
{
testRunModel.Regions.Add(region);
}
TestRun testRun = _db.TestRuns.OrderByDescending(x => x.Id).First();
foreach (TestRunProcessRegion region in testRun.GetProcessesForRegion(_db.Regions.Single(i => i.Id == id)))
{
ProcessModel process = new ProcessModel
{
Code = region.ProcessRegion.Process.Code,
Description = region.ProcessRegion.Process.Description,
Name = region.ProcessRegion.Process.Name,
Process = region.ProcessRegion.Process.Id
};
foreach (SubProcess subProcess in region.ProcessRegion.Process.SubProcesses)
{
foreach (Risk risk in subProcess.Risks)
{
foreach (Test test in risk.Tests)
{
TestModel testModel = new TestModel
{
Id = test.Id,
Name = test.Name,
Description = test.Description,
ProcessId = region.ProcessRegion.Process.Id,
RegionId = region.ProcessRegion.Id
};
process.Tests.Add(testModel);
}
}
}
testRunModel.Processes.Add(process);
}
var users = new List<User>();
foreach (User user in _db.Users)
{
users.Add(new User
{
Id = user.Id,
Name = user.Name,
});
}
ViewBag.Users = users;
return View(testRunModel);
}
//
// POST: /TestPreparation/Create
[HttpPost]
public ActionResult Create(string[] OwnerId, string[] selectedTests, string[] processes)
{
if (ModelState.IsValid)
{
//_db.TestRunStatus.Add(testrunstatus);
//_db.SaveChanges();
return RedirectToAction("Index");
}
return View();
}
The reason why you are not getting any data back is because the method signature of your Post Action needs to be
public ActionResult Create(string OwnerId ...) //preferably int depending on what OwnerId is
This is because you only select one item out of the drop down box. So if you use this signature as opposed to string[], the Model binder will pass the selected value back to your action.
Having said this, it is better practice and the "MVC Way" to use,
Html.DropDownFor(x => x.UserID) it really makes things easier :)
This applies to all html input controls:
http://www.asp.net/mvc/tutorials/getting-started-with-aspnet-mvc3/cs/examining-the-edit-methods-and-edit-view
UPDATE
I think the best thing to do would be to add an OwnerID to the ProccessModel class.
Becuase ProccessModel looks to be IEnumberable<ProccessModel> Processes contained in the ViewModel you can get the Model to Bind in the following way using the defult MVC model binder.
Phil Haack has bloged about binding to lists here:
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
Adapting from Phil's post I think you will have to do something like this:
<% for (int i = 0; i < Model.Processes.Count; i++) { %>
<%: Html.SelectListFor(model => model.Processes[i].OwnerID, (IEnumerable<SelectListItem>)ViewBag.Users) %>
<% } %>
Change the ViewBag.User to:
var users = _db.Users.Select(x => new SelectListItem(){
text = x.Name,
value = x.Value
});
Modify the Post Action:
[HttpPost]
public ActionResult Create(TestRunModel model)
{
foreach(var process in model.Porcesses)
{
process.OwnerID // This should be a user selected value
}
// code removed for brevity
}
I could help with getting the TestModel values if you like but I need to do some work now ;)

How to use a dynamic var in ActionLink to call a different controller's action

Part of ControllerA:
public ActionResult Index()
{
ViewBag.Message = "ToolScope Testing";
var Baselines = from b in db.Baselines
orderby b.Name
select b;
ViewBag.Baselines = Baselines;
return View();
}
Part of View for ControllerA
#foreach (var item in #ViewBag.Baselines)
{
<tr>
<li> #Html.ActionLink( item.Name, "Details", "BaseLine",new { id = item.BaselineID }, null) </li>
</tr>
}
The item.Name is causing problem, however, it works if I use something like
<li> #Html.ActionLink( "SomeName", "Details", "BaseLine",new { id = item.BaselineID }, null) </li>
What should I do to have the dynamic names, i.e., the first ActionLink?
P.S.: I am new to MVC
I see you are new to MVC. Good news, you've already gotten the V(iew) and the C(ontroller). Now it's time to master the M(odel). In your example, you are using the ViewBag to transport knowledge from the Controller to your View. This is a typical responsibility of the Model. So you need to create a new class in your Models directory. It will probably look something like this:
public class MyFirstModel
{
public IEnumerable<MyCustomType> Baselines { get; set; }
public MyFirstModel() { }
}
Edit your Controller
public ActionResult Index()
{
ViewBag.Message = "ToolScope Testing";
var baselines = from b in db.Baselines
orderby b.Name
select b;
var model = new MyFirstModel
{
Baselines = baselines
};
return View(model);
}
Then, add this to the top of your View:
#model MvcApplication.Models.MyFirstModel
Now you can use this code in your view instead:
#foreach (var item in Model.BaseLines)
{
<tr>
<li> #Html.ActionLink( item.Name, "Details", "BaseLine",new { id = item.BaselineID }, null) </li>
</tr>
}

Accessing HTML Form data in MVC controller

I have this problem. I need to access data that the user inputs via the statement during the controller method.
Here's my code, maybe this will make it more clear:
// client side
#using (Html.BeginForm())
{
if (competitorList.Count > 0 && eventList.Count > 0)
{
foreach (Event evt in eventList)
{
<table>
<tr><th>#evt.activity.Name</th></tr>
<tr>
<th>Name</th>
<th>Email</th>
<th>Score</th>
<th>New Score</th>
<th>Update</th>
</tr>
#foreach (Results res in resultList)
{
if (res.EventID == evt.id)
{
string competitorName = Person.getUserByEmail(res.CompetitorEmail).FirstName + Person.getUserByEmail(res.CompetitorEmail).LastName;
<tr>
<td>#competitorName</td>
<td>#res.CompetitorEmail</td>
<td>#res.Score</td>
<td><form action="EventResults"><input type="text" name="score" id="score" /></form></td>
<td>#Html.ActionLink("Update", "UpdateResults", "Competition", new { compId = evt.competitionId, evtId = res.EventID, email = res.CompetitorEmail }, null)</td>
</tr>
}
}
</table>
<hr />
}
}
else
{
<p>There are currently no competitors invited to participate</p>
}
}
// controller
public ActionResult UpdateResults(FormCollection form, int compId, int evtId, string email)
{
////// this returns 0.0 /////
double score = Convert.ToDouble(form["score"]);
BINC.Models.Results.UpdateResults(evtId, email, score);
List<Event> CompetitionEvents = Event.getEventsByCompetitionId(compId);
ViewBag.CompetitionEvents = CompetitionEvents;
List<Competitor> Competitors = Competitor.getCompetitors(compId);
ViewBag.Competitors = Competitors;
List<Results> Results = Competition.getCompetitorResultsPairings(CompetitionEvents, Competitors);
ViewBag.Results = Results;
ViewBag.Competition = Competition.getCompetitionById(compId);
return View("EventResults");
}
What you see in the controller method doesn't work; I assume it's because the page wasn't actually "submitted"? I really want to use a link instead of a submit button though. Can someone give me a hand?
give it the ActionType like [HttpPost],[HttpGet],[HttpDelete]
[HttpPost]
public ActionResult UpdateResults(FormCollection form, int compId, int evtId, string email)
{
//Code
}
If you want to use a link, you are using a GET request not a post request.
Your options using a link are either make it an ajax request (see my prior answer at MVC3 Html.ActionLink Post)
or use javascript to post the form:
How can I use an anchor tag to submit a form with jquery
$(document).ready(function(){
$("a").click(function(){
$("#requestNew").submit();
});
});
or using $("#yourHrefId") if you want to refer by id rather than all hrefs.

Resources