Modelbinding lists - asp.net-mvc

I got a controller action like
public class Question {
public int Id { get;set; }
public string Question { get;set; }
public string Answer { get;set; }
}
public ActionResult Questions()
{
return View(GetQuestions());
}
public ActionResult SaveAnswers(List<Question> answers)
{
...
}
the view> looks like:
<% for (int i = 0; i < Model.Count; i++) { %>
<div>
<%= Html.Hidden(i.ToString() + ".Id") %>
<%= Model[i].Question %>
<%= Html.TextBox(i.ToString() + ".Answer") %>
</div>
<% } %>
Obviously this view doesn't work. I'm just not able access the list in the view.
The documentation for this also is outdated, it seem a lot of the functionality around modelbinding lists where changed in the beta.

I think that Scott Hanselman's post probably holds the answer. However it appears that you are trying to tie you view references to an anonymous object by returning in the post ...0.Answer=answer...
You should instead I believe be tying your fields to the `List answers refering to the answers[index].Answer.
Try the following:
<% for (int i = 0; i < Model.Count; i++) { %>
<div>
<%= Html.Hidden("answer["+i.ToString() + "].Id", Model["+i.ToString() + "].Id) %>
<%= Model[i].Question %>
<%= Html.TextBox("answer["+i.ToString() + "].Answer", Model["+i.ToString() + "].Answer) %>
</div>
<% } %>
Richard

Take a look at this and this question. Also this blog post.
Edit : As for accessing the model in the view. Are you sure you declared your with the following attribute?
<%# Page Language="C#"
Inherits="System.Web.Mvc.ViewPage<List<Namespace.Question>>" %>
//Assuming the GetQuestions() method returns a list of question objects.

the answer is not to use the html helpers.
<% for (int i = 0; i < Model.Count; i++) { %>
<div>
<input type="hidden" name="answers[<%= i %>].Id" id="answers_<%= i %>_Id" value="<%= Model[i].Id %>" />
<input type="text" name="answers[<%= i %>].Answer" id="answers_<%= i %>_Answer" value="<%= Model[i].Answer %>" />
</div>
<% } %>
Not very pretty, but works. The important thing is that Name and Id need to be different.
Name is allowed to have "[", "]" but id isn't.

Related

ASP.NET MVC Sequence contains no elements

I have the following code in my HomeController:
public ActionResult Edit(int id)
{
var ArticleToEdit = (from m in _db.ArticleSet where m.storyId == id select m).First();
return View(ArticleToEdit);
}
[ValidateInput(false)]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(Article ArticleToEdit)
{
var originalArticle = (from m in _db.ArticleSet where m.storyId == ArticleToEdit.storyId select m).First();
if (!ModelState.IsValid)
return View(originalArticle);
_db.ApplyPropertyChanges(originalArticle.EntityKey.EntitySetName, ArticleToEdit);
_db.SaveChanges();
return RedirectToAction("Index");
}
And this is the view for the Edit method:
<% using (Html.BeginForm()) {%>
<fieldset>
<legend>Fields</legend>
<p>
<label for="headline">Headline</label>
<%= Html.TextBox("headline") %>
</p>
<p>
<label for="story">Story <span>( HTML Allowed )</span></label>
<%= Html.TextArea("story") %>
</p>
<p>
<label for="image">Image URL</label>
<%= Html.TextBox("image") %>
</p>
<p>
<input type="submit" value="Post" />
</p>
</fieldset>
<% } %>
When I hit the submit button I get the error: Sequence contains no elements on this line: var originalArticle = (from m in _db.ArticleSet where m.storyId == ArticleToEdit.storyId select m).First();
What is the problem? How do I fix it. Thanks
The problem is you have nothing that matches your linq query in _db.ArticleSet. First will throw against an empty collection.
Try FirstOrDefault() if returning null is ok. FirstOrDetault() will return null if nothing matches.
You are not including the ID of the article in your HTML form. If you debug the ArticleToEdit object is probably either null or has zero storyId.
You should include your storyId in the HTML form. You can do it as a hidden field if you don't want the user to see it. For example:
<% using (Html.BeginForm()) {%>
<%= Html.HiddenFor("storyId") %>
...
On a separate note, you should probably switch to using Single instead of First. First indicates that you want the First item in a collection. Single indicates that you should get one and only one result.
You need to be sure of is that when you're editing a record, you need to tell the database what record to edit. It's not enough to have the ID in the querystring unless you specifically tell the Model to use that ID (see my second option), however the easiest way to do that is add the field in your form.
<% using (Html.BeginForm()) {%>
<%= Html.HiddenFor("storyId") %>
<fieldset>
<legend>Fields</legend>
<p>
<label for="headline">Headline</label>
<%= Html.TextBox("headline") %>
</p>
<p>
<label for="story">Story <span>( HTML Allowed )</span></label>
<%= Html.TextArea("story") %>
</p>
<p>
<label for="image">Image URL</label>
<%= Html.TextBox("image") %>
</p>
<p>
<input type="submit" value="Post" />
</p>
</fieldset>
<% } %>
This second option shows how to specify the storyId after the fact by submitting it to the Action via the Querystring. You just have to make sure the form action includes ?storyId=[n]
[ValidateInput(false)]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(Article ArticleToEdit, int storyId)
{
ArticleToEdit.storyId = storyId;
if (ModelState.IsValid) {
_db.ApplyPropertyChanges(originalArticle.EntityKey.EntitySetName, ArticleToEdit);
_db.SaveChanges();
return RedirectToAction("Index");
} else {
return View(ArticleToEdit);
}
}
I would also suggest using something like AutoMapper to map ArticleToEdit to ArticleSet so that you don't need to make an additional DB lookup just to grab the original article. Basically if you are able to map ArticleToEdit to the ArticleSet Model, then you can use LINQ to SQL to perform the update without first querying for the storyId.

Many to Many with LINQ-To-Sql and ASP.NET MVC

I will restrict this to the three tables I am trying to work with Problem, Communications, and ProbComms. The scenario is that a Student may have many Problems concurrently which may affect their studies. Lecturers may have future communications with a student after an initial problem is logged, however as a Student may have multiple Problems the Lecturer may decide that the discussion they had is related to more than one Problem.
Here is a screenshot of the LINQ-To-Sql representation of my DB:
LINQ-To-Sql Screenshot
At the moment in my StudentController I have a StudentFormViewModel Class:
//
//ViewModel Class
public class StudentFormViewModel
{
IProbCommRepository probCommRepository;
// Properties
public Student Student { get; private set; }
public IEnumerable<ProbComm> ProbComm { get; private set; }
//
// Dependency Injection enabled constructors
public StudentFormViewModel(Student student, IEnumerable<ProbComm> probComm)
: this(new ProbCommRepository())
{
this.Student = student;
this.ProbComm = probComm;
}
public StudentFormViewModel(IProbCommRepository pRepository)
{
probCommRepository = pRepository;
}
}
When I go to the Students Detail Page this runs:
public ActionResult Details(string id)
{
StudentFormViewModel viewdata = new StudentFormViewModel(studentRepository.GetStudent(id),
probCommRepository.FindAllProblemComms(id));
if (viewdata == null)
return View("NotFound");
else
return View(viewdata);
}
The GetStudent works fine and returns an instance of the student to output on the page, below the student I output all problems logged against them, but underneath these problems I want to show the communications related to the Problem.
The LINQ I am using for ProbComms is This is located in the Model class ProbCommRepository, and accessed via a IProbCommRepository interface:
public IQueryable<ProbComm> FindAllProblemComms(string studentEmail)
{
return (from p in db.ProbComms
where p.Problem.StudentEmail.Equals(studentEmail)
orderby p.Problem.ProblemDateTime
select p);
}
However for example if I have this data in the ProbComms table:
ProblemID CommunicationID
1 1
1 2
The query returns two rows so I assume I somehow have to groupby Problem or ProblemID but I am not too sure how to do this with the way I have built things as the return type has to be ProbComm for the query as thats what Model class its located in.
When it comes to the view the Details.aspx calls two partial views each passing the relevant view data through, StudentDetails works fine page:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MitigatingCircumstances.Controllers.StudentFormViewModel>" %>
<% Html.RenderPartial("StudentDetails", this.ViewData.Model.Student); %>
<% Html.RenderPartial("StudentProblems", this.ViewData.Model.ProbComm); %>
StudentProblems uses a foreach loop to loop through records in the Model and I am trying another foreach loop to output the communication details:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<MitigatingCircumstances.Models.ProbComm>>" %>
<script type="text/javascript" language="javascript">
$(document).ready(function() {
$("DIV.ContainerPanel > DIV.collapsePanelHeader > DIV.ArrowExpand").toggle(
function() {
$(this).parent().next("div.Content").show("slow");
$(this).attr("class", "ArrowClose");
},
function() {
$(this).parent().next("div.Content").hide("slow");
$(this).attr("class", "ArrowExpand");
});
});
</script>
<div class="studentProblems">
<% var i = 0;
foreach (var item in Model) { %>
<div id="ContainerPanel<%= i = i + 1 %>" class="ContainerPanel">
<div id="header<%= i = i + 1 %>" class="collapsePanelHeader">
<div id="dvHeaderText<%= i = i + 1 %>" class="HeaderContent"><%= Html.Encode(String.Format("{0:dd/MM/yyyy}", item.Problem.ProblemDateTime))%></div>
<div id="dvArrow<%= i = i + 1 %>" class="ArrowExpand"></div>
</div>
<div id="dvContent<%= i = i + 1 %>" class="Content" style="display: none">
<p>
Type: <%= Html.Encode(item.Problem.CommunicationType.TypeName) %>
</p>
<p>
Problem Outline: <%= Html.Encode(item.Problem.ProblemOutline)%>
</p>
<p>
Mitigating Circumstance Form: <%= Html.Encode(item.Problem.MCF)%>
</p>
<p>
Mitigating Circumstance Level: <%= Html.Encode(item.Problem.MitigatingCircumstanceLevel.MCLevel)%>
</p>
<p>
Absent From: <%= Html.Encode(String.Format("{0:g}", item.Problem.AbsentFrom))%>
</p>
<p>
Absent Until: <%= Html.Encode(String.Format("{0:g}", item.Problem.AbsentUntil))%>
</p>
<p>
Requested Follow Up: <%= Html.Encode(String.Format("{0:g}", item.Problem.RequestedFollowUp))%>
</p>
<p>Problem Communications</p>
<% foreach (var comm in Model) { %>
<p>
<% if (item.Problem.ProblemID == comm.ProblemID)
{ %>
<%= Html.Encode(comm.ProblemCommunication.CommunicationOutline)%>
<% } %>
</p>
<% } %>
</div>
</div>
<br />
<% } %>
</div>
The issue is that using the example data before the Model has two records for the same problem as there are two communications for that problem, therefore duplicating the output.
Any help with this would be gratefully appreciated.
Thanks,
Jon
I have recently struggled greatly with many to many relationships in MVC. I've finally gotten mine working. I'd love to help you out, but do not fully understand your problem.
When you say that the query returns 2 rows, I would think that it should since there are 2 communications for that problem. If you want to return problems for a student, you do not need to query the ProbComms table, just the Problem table. Then if you want to return communications for those problems, query the ProbComms table.
For your loop, you need to loop through the problems and then within that loop, loop through the communications specific to that problem. Something very loosely like:
foreach (var p in Model.Student.Problem)
{
<%= Html.Encode(p.ProblemInfoField) %>
...
foreach (var c in p.ProbComms)
{
<%= Html.Encode(c.Communications.CommunicationInfoField) %>
...
}
}
You do not need to select the ProbComms in the controller and send them to the view. They should already be linked. Only the student needs to be sent to the view.

Proper submission of forms with autogenerated controls

Based on:
MVC Html.CheckBox and form submit issue
Let's consider following example. View:
<% using(Html.BeginForm("Retrieve", "Home")) %>
<% { %>
<%foreach (var app in newApps) { %>
<tr>
<td><%=Html.CheckBox(""+app.ApplicationId )%></td>
</tr>
<%} %>
<input type"submit"/>
<% } %>
Controller:
List<app>=newApps; //Database bind
for(int i=0; i<app.Count;i++)
{
var checkbox=Request.Form[""+app[i].ApplicationId];
if(checkbox!="false")// if not false then true,false is returned
}
Proposed solution was about manual parsing of Request.Form that seems for me out of MVC concept. It makes the problem while unit testing of this controller method. In this case I need to generate mock Request.Form object instead of some ViewModel passed as input param.
Q: Is there some other solution of submitting forms like this, so that ViewModel object, containing collection of submitted controls, passed as input param to controller method?
For example:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Retrieve(AppList[] applist)
or
public ActionResult Retrieve(AppList<App> applist)
etc
Controller:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Retrieve(AppList[] applist)
View:
<% using(Html.BeginForm("Retrieve", "Home")) %> { %>
<%foreach (var app in newApps) { %>
<tr>
<td><%=Html.CheckBox(String.Format("appList[{0}].AProperty", app.ApplicationId) %></td>
</tr>
<% } %>
<input type"submit" />
<% } %>
Read this: Scott Hanselman's ComputerZen.com - ASP.NET Wire Format for Model Binding to Arrays, Lists, Collections, Dictionaries
UPDATED:
If ApplicationId is a key from DB it is better to use AppList<App> as Action parameter. Then your form would be looking as:
<% using(Html.BeginForm("Retrieve", "Home")) %> { %>
<% var counter = 0; %>
<% foreach (var app in newApps) { %>
<tr>
<td><%=Html.CheckBox(String.Format("appList[{0}].Key", counter), app.ApplicationId) %></td>
<!-- ... -->
<td><%=Html.Input(String.Format("appList[{0}].Value.SomeProperty1", counter), app.SomeProperty1) %></td>
<td><%=Html.Input(String.Format("appList[{0}].Value.SomePropertyN", counter), app.SomePropertyN) %></td>
<% counter = counter + 1; %>
</tr>
<% } %>
<input type"submit" />
<% } %>

In a View, What is the most efficient way to hide HTML related to a null value from the Model?

This is likely a very simple question with a straightforward answer but I'm something of a newbie when it comes to ASP.NET (MVC).
I am returning an address (in pieces) from my model. Some of components are null. Is there a simple or fluent-like way to check for that null value without a lot of extra code to determine whether or not to display the associated surrounding HTML (not just the value)?
Example:
<% foreach (var item in Model)
{ %>
<h3>
<%= Html.ActionLink(item.name, "Details", new { id = item.ID})%></h3>
<div>
<%= Html.Encode(item.address) %><br />
<%= Html.Encode(item.city) %>,
<%= Html.Encode(item.state) %>
<%= Html.Encode(item.zip) %>
</div>
<% } %>
In the above example, if there is a null value for item.address, I want the <br/> tag to be hidden as well so that only the city, state zip string is displayed.
I'm looking for something more elegant than just putting a <% if () { %> conditional out there. Thanks.
You could write an extension method for HtmlHelper that checked to see if it was null or not, and would output nothing if it was, or field + <br /> if it wasn't.
public static string FieldLine(this HtmlHelper helper, object value, bool addBr)
{
if (value == null)
{
return string.Empty;
}
else if (addBr)
{
return helper.Encode(value) + "<br />";
}
else
{
return helper.Encode(value);
}
}
Remember to import the namespace of your extension class into your View aspx. For this example, if my namespace was "MvcApplication1.Extensions", I would use
<%# Import Namespace="MvcApplication1.Extensions" %>
at the top of my View. Then to use it, it would simply be:
<%= Html.FieldLine(item.address, true) %>
<%= Html.FieldLine(item.city, true) %>
etc.
I'm adding another answer based on what womp described above.. I'd make the helper a bit more generic than he did, and still honor the origional Encode as well...
public static string EncodeWithHtml(this HtmlHelper helper, object value, string html)
{
if (value == null)
{
return string.Empty;
}
else
{
return helper.Encode(value) + html;
}
}
This would allow you to do something like:
<%= Html.EncodeWithHtml(item.address, "<br />") %>
or
<%= Html.EncodeWithHtml(item.address, "<img src=\"images\home.gif\"><br />") %>
Assuming item.address is a string...
<%= Html.Encode(item.address) %>
<% if (!string.IsNullOrEmpty(item.address)) { %>
<br />
<% } %>
of course, this is typed out in the little comment box, so be wary of spelling, case, etc, etc.

Populating a form dynamically based on user input in ASP.Net MVC

My question is similar to Engram's here, but my question goes a bit further. The way i intend it to work is I have a textbox asking how many entries a user is going to make. After they input the number, I need to create that many more textboxes to allow for entries (and then repeat the same process with those textboxes, but baby steps first...) I tried collecting the keys on the post, but it only returns the initial textbox asking for the number of entries. I'm still trying to get a grasp on MVC and the tutorials/videos so far don't delve this deep into it yet. Then again, I know this is probably something I could handle using JQuery, but I'd still be stuck in the same situation.
This is the controller I'm using:
[AcceptVerbsAttribute("POST")]
public ActionResult Create(int tbxNumberOfExercises)
{
ViewData["number"] = tbxNumberOfExercises;
foreach (var key in Request.Form.Keys)
{
string keyString = key.ToString();
if (keyString.StartsWith("tbox_exercise", StringComparison.OrdinalIgnoreCase))
{
string recNum = keyString.Substring(13, keyString.Length - 13);
string approvedKey = Request.Form["tbox_exercise" + recNum];
int number;
int.TryParse(approvedKey, out number);
}
}
return View("Create");
}
And this is my aspx:
<form action="/CreateWorkout/Create" method="post">
Number of Exercises:
<%= Html.TextBox("tbxNumberOfExercises") %>
<br />
<br />
<input type="submit" value="Set Exercise Number" />
</form>
<% if (ViewData["number"] != null)%>
There are this many:<%=Html.Encode(ViewData["number"])%>
<br />
and this line should show up
<% if (ViewData["number"] != null)
{
int max = (int)ViewData["number"];
for (int i = 0; i < max; i++)
{%>
<br />
<br />
<%= Html.TextBox("tbox_exercise" + i) %>
<% }
} %>
<% if (ViewData["s"] != null) %>
<%=Html.Encode(ViewData["s"]) %>
Is there something I'm overlooking, not comprehending, or should I quit while I'm at it because it seems like I'll never get it?
Thanks in advance for any help -- I'm just trying to learn as most I can.
I'd break this up in stages, you'll need to add a "Save" view someplace depending on what you want.
Scott
<form action="/Demo01/Create" method="post">
Number of Exercises:
<%= Html.TextBox("tbxNumberOfExercises") %>
<br />
<br />
<input type="submit" value="Set Exercise Number" />
</form>
<% if (ViewData["number"] != null) {%>
<form action="/Demo01/Save" method="post">
There are this many:<%=Html.Encode(ViewData["number"])%>
<br />
and this line should show up
<% if (ViewData["number"] != null) {
int max = (int)ViewData["number"];
for (int i = 0; i < max; i++) {%>
<br />
<br />
<%= Html.TextBox("tbox_exercise" + i) %>
<% }
} %>
<% if (ViewData["s"] != null) %>
<%=Html.Encode(ViewData["s"]) %>
<input type="submit" value="Save Exercises" />
<% } %>
</form>
And then in your controller something like this:
public class Demo01Controller : Controller {
public ActionResult Create() {
return View();
}
[AcceptVerbsAttribute("POST")]
public ActionResult Create(int tbxNumberOfExercises) {
ViewData["number"] = tbxNumberOfExercises;
return View("Create");
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Save() {
foreach (var key in Request.Form.Keys) {
string keyString = key.ToString();
if (keyString.StartsWith("tbox_exercise", StringComparison.OrdinalIgnoreCase)) {
string recNum = keyString.Substring(13, keyString.Length - 13);
string approvedKey = Request.Form["tbox_exercise" + recNum];
int number;
int.TryParse(approvedKey, out number);
}
}
return View(); // return/redirect to wherever you want
}
}
I would consider adding the textboxes client-side via javascript rather than posting back to the server to have the form redrawn, assuming that you can live with javascript as a requirement for using the application. If not, then #Scott's approach should work. As a matter of preference I would probably have the Save method take a FormCollection parameter rather than deal with the Request object directly.
The javascript solution would be to have a single textbox and a button to add another. The user could continue to add textboxes until they have enough.
The problem is that your </form> ending tag needs to come at the end of your view.
Try this modified view:
<form action="/CreateWorkout/Create" method="post">
Number of Exercises:
<%= Html.TextBox("tbxNumberOfExercises") %>
<br />
<br />
<input type="submit" value="Set Exercise Number" />
<% if (ViewData["number"] != null)%>
There are this many:<%=Html.Encode(ViewData["number"])%>
<br />
and this line should show up
<% if (ViewData["number"] != null)
{
int max = (int)ViewData["number"];
for (int i = 0; i < max; i++)
{%>
<br />
<br />
<%= Html.TextBox("tbox_exercise" + i) %>
<% }
} %>
<% if (ViewData["s"] != null) %>
<%=Html.Encode(ViewData["s"]) %>
</form>
I would recommend Scott's approach as far as best practice. This answer is about getting your exact scenario working.
Thanks for the help guys. I realized at 5 this morning that my issue is that the form didn't include the new textboxes / I needed another form. I'll have to seriously look into Javascript and actually modifying the DOM as it would be better to keep it client side.
Thanks so much.

Resources