Proper submission of forms with autogenerated controls - asp.net-mvc

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

Related

Null reference exception error

I have a Student data model(Entity Framework) where I have set both "StudentID" and "StudentName" as primary keys. StudentID is of type Int and StudentName is of type String.
I created a strongly-typed view, But when I run it i get the following error:
Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.
Source Error:
Line 10: <tr>
Line 11: <td>
Line 12: <%= Html.Encode(item.StudentID) %>**
Line 13: </td>
Line 14: <td>
Here is my controller action:
public ActionResult Index()
{
ViewData.Model = student.StudentTable;
return View();
}
Here is the View:
<%# Page
Language="C#"
Inherits="System.Web.Mvc.ViewPage<IEnumerable<Student.Models.StudentTable>>" %>
<html>
<head runat="server">
</head>
<body>
<table>
<% foreach (var item in Model) { %>
<tr>
<td>
<%= Html.Encode(item.StudentID) %>
</td>
<td>
<%= Html.Encode(item.StudentName) %>
</td>
</tr>
<% } %>
</table>
</body>
</html>
Without any additional information, my guess is that item is null. If the Student Table has a SINGLE StudentID per record, then you need to simply pass model.StudentID
Controller
public ActionResult Index()
{
var model = student.StudentTable;
return View(model);
}
aspx
<% foreach (var item in Model) { %>
<tr>
<td>
<%= Html.Encode(item.StudentID) %>
</td>
<td>
<%= Html.Encode(item.StudentName) %>
</td>
</tr>
<% } %>
I doubt item is null, if it were null you wouldn't get inside the loop. Set a break point and examine item, it's probably not what you think it is.
If this is the line you're getting the exception on, then your item variable must be null. You'll need to look closely at how that's being populated - if your model is null, then you should be able to throw a debugger on your controller action and figure out why that's not working.
Could be a few things ...
Are you passing up valid view data from the ActionMethod?
Have you defined #model on the view?
Assuming student.StudentTable is a single object with StudentID property then you need to change your view code to Model.StudentID

Passing SelectList through ViewData to editor templates - not displayed properly

It's a bit complicated so bear with me.
Let's say I've got an example of a controller edit action defined like:
Node nd = _repo.getNode(id);
List<Category> ac = new List<Category>();
ac.AddRange(_repo.getCategories());
SelectList acl = new SelectList(ac, "category_id", "category_name", ac.Where(cat => cat.category_id == nd.category_id).First());
ViewData["category_id"] = acl;
return View(nd);
The view is templated like so:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Myapp.Models.Node>" %>
<% if (ViewData.TemplateInfo.TemplateDepth > 1)
{ %>
<%= ViewData.ModelMetadata.SimpleDisplayText %>
<% }
else
{ %>
<table cellpadding="0" cellspacing="0" border="0">
<% foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForEdit && !ViewData.TemplateInfo.Visited(pm)))
{ %>
<% if (prop.HideSurroundingHtml)
{ %>
<%= Html.Editor(prop.PropertyName) %>
<% }
else
{ %>
<tr>
<td>
<div class="editor-label" style="text-align: right;">
<%= prop.IsRequired ? "*" : ""%>
<%= Html.Label(prop.PropertyName)%>
</div>
</td>
<td>
<div class="editor-field">
<% if (ViewData.Keys.Contains(prop.PropertyName))
{
if ((ViewData[prop.PropertyName]).GetType().Name == "SelectList")
{ %>
<%= Html.DropDownList(prop.PropertyName, (SelectList)ViewData[prop.PropertyName])%>
<% }
else
{ %>
<%= Html.Editor(prop.PropertyName)%>
<% } %>
<% }
else
{ %>
<%= Html.Editor(prop.PropertyName)%>
<% } %>
<%= Html.ValidationMessage(prop.PropertyName, "*")%>
</div>
</td>
</tr>
<% } %>
<% } %>
</table>
<% } %>
So, what the template does is display a dropdown list for every property for which ViewData["property_name"] exists.
I've also defined DisplayName metadata attributes for every property of my Node class.
Now, the dropdown lists display fine and are being populated correctly, but:
The first value from a list is always selected, even though the SelectList selected value predicate is fine and does set a proper value (in the debugger at least).
Html.Label in the template returns a proper DisplayName for properties, but when I define a ViewData for them so as to display the dropdown list, the label resets to normal property name (ie. category_id instead of Category).
What gives? Can you think of any "neater" way to accomplish this functionality?
Allright, no one's answering so there's my answer, maybe it comes in handy for someone:
Do not use your property names for ViewData keys! It messes up with the view model, so your views get confused and start to behave strangely.
Actually, best avoid the magic strings mess entirely, but if you insist, just use something like ex.: ViewData[prop.PropertyName+"_list"]. Your views are going to be fine now.

ajax.beginform inside a for loop

so I am using a foreach loop to iterate through comments. The comment section is wrapped inside "Comments" div. My function DeleteComment fetches comments again once you delete a comment and rebinds it to the control. However, after you delete a comment, anytime you try to delete another comment, the commentId of the very first deleted comment would keep getting passed to DeleteComment function instead of the passing the commentId of the comment you are trying to delete. If you refresh the page, then you can delete ONE comment again, and the same problem if you try to delete another.
<table>
<% foreach (var item in Model) { %>
<tr> <td>item.Comment </td>
<td>
<%if (HttpContext.Current.User.Identity.IsAuthenticated && item.Poster.UserName == HttpContext.Current.User.Identity.Name)
{ %>
<%using (Ajax.BeginForm("DeleteComment", "Home", new AjaxOptions {UpdateTargetId = "Comments"}))
{%>
<%=Html.Hidden("articleId", item.CarrierId) %>
<%=Html.Hidden("num_comments", (int)ViewData["num_comments"]) %>
<%=Html.Hidden("commentId", item.CommentId) %>
<input type = "submit" value = "delete" />
<%} %>
<%} %>
</td>
</tr>
<table>
For example, you delete comment with commentId 1... then you try to delete some other comment, but commentId 1 would keep getting passed. You refresh the page, problem gone... for one comment...
<div id = "Comments">
<%if ((int)ViewData["num_comments"] > 0)
{ %>
<%Html.RenderPartial("CommentsX", Model.Comments, new ViewDataDictionary{{"num_comments", ViewData["num_comments"] }}); %>
<%} %>
</div>
where CommentsX is the ascx control that contains the for loop code I posted above.
Sometimes having multiple elements on the same page with the same ids can cause funky results like you are seeing. Try making the id's unique like below:
<table>
<% int index=1;
foreach (var item in Model) { %>
<tr> <td>item.Comment </td>
<td>
<%if (HttpContext.Current.User.Identity.IsAuthenticated && item.Poster.UserName == HttpContext.Current.User.Identity.Name)
{ %>
<%using (Ajax.BeginForm("DeleteComment", "Home", new AjaxOptions {UpdateTargetId = "Comments"}))
{%>
<%=Html.Hidden("articleId" + index, item.CarrierId) %>
<%=Html.Hidden("num_comments" + index, (int)ViewData["num_comments"]) %>
<%=Html.Hidden("commentId" + index, item.CommentId) %>
<input type = "submit" value = "delete" />
<%} %>
<% index++;
} %>
</td>
</tr>
<table>

How do I handle a filtered and unfiltered list in the same asp.net mvc view?

I need a way to display whether a user has subscribed to a service or not and to display it into a table in a view. So far I am able to show if that a user has subscribed to a service but am not able to show when they have not subscribed.
I created a viewmodel like this:
public class MyServicesController : Controller
{
public ActionResult Index()
{
WillEntities we = new WillEntities();
var servicegroups = we.ServiceGroupSet.ToList();
var services = we.ClientServiceSet.Include("AspnetUser").ToList();
var aspnetusers = we.AspnetUserSet.Include("ClientService").ToList();
return View(new MyServicesViewModel (servicegroups, services, aspnetusers));
}
}
Then rendered the services table to the view like this:
<div id="Services">
<h2>My Services</h2>
<table>
<tr>
<th class="th1">
Service Name
</th>
<th class="th3">
Select / Deselect
</th>
</tr>
</table>
<% foreach (var servgroup in Model.ServiceGroups )
{ %>
<ul> <%= servgroup.GroupName %> </ul>
<table>
<% foreach (var serv in servgroup.ClientService )
{ %>
<tr>
<td class="td1">
<%= serv.Description%>
</td>
<td class="td3">
<%foreach (var user in serv.AspnetUser)
{ %>
<%if (user.UserName.Equals(User.Identity.Name, >StringComparison.OrdinalIgnoreCase))
{ %>
Already subscribed
<% }
else
{%>
<%} %>
<%} %>
</td>
</tr>
<% } %>
</table>
<% } %>
</div>
Which works fine at pulling and filtering any users subscribed to that service then seeing if the current user is in that list.
But given that by filtering in this way by using the <%foreach (var user in serv.AspnetUser) clause I am pulling only from the list of users that have chosen a particular given service.
So I have no way at the moment to grab the unchosen services and to display the "Not yet subscribed" string to render into that service's row in the table.
I tried creating a boolean function to pull out the "user" var above and return a true or false of whether the current user has subscribed to that service:
public class MyServicesViewModel
{
public bool UsersServices(AspnetUser user)
{
if (user.UserId.ToString() == "35l1cob9-rest_of_user_guid-96975")
return (true);
else
return (false);
}
}
but could not figure out a way to get it into the view, and get the feeling I am overcomplicating it by trying this method.
Can anyone see a less complicated way to do this?
Thanks a lot,
Paul
It looks like you're using Entity Framework for your ORM? I'm more familiar with Linq to SQL, and it's difficult to give you an exact solution without knowing all the behind the scenes details - but assuming you have the appropriate primary/foreign key relationships setup in your db - you should be able to use linq to query the information you need:
<table>
<tr>
<td class="td1">
<%= serv.Description%>
</td>
<td class="td3">
<% if (serv.AspnetUser.Where(u => u.UserName == User.Identity.Name).Count() > 0)
{ %>
Already subscribed
<% }
else
{ %>
Not subscribed
<%
} %>
</td>
</tr>
</table>
Hope that helps.

Ajax.ActionLink, how to send the selected object in the partial view? asp.net mvc

I guess this is a noob question, but here it comes:
I have a list of products:
<% foreach (var item in Model) { %>
<tr>
<td>
<%= Html.Encode(item.code) %>
</td>
<td>
<%= Html.Encode(String.Format("{0:g}", item.date)) %>
</td>
<td>
<%= Html.Encode(item.category) %>
</td>
<td>
<%= Html.Encode(item.description) %>
</td>
<td>
<%= Html.Encode(String.Format("{0:F}", item.price)) %>
</td>
......
}
And a partial view after all of these (in the same page):
<div id="productForEdit">
<fieldset>
<legend>Your Selected Product</legend>
<% Html.RenderPartial("~/Views/Products/Edit", productObject); %>
</fieldset>
</div>
How do I use Ajax.ActionLink, so that when I will click the description of a product, the product will be plugged in the Partial View from the bottom of the page?
I tried some combination with UpdateTargetId="productForEdit", but I had no success.
The purpose is to have a quick edit tool in the page.
I think this should work:
<td>
<%= Ajax.ActionLink(Html.Encode(item.description), /* link text */
"GetProduct", /* action name */
"Product", /* controller name */
new { productCode = Model.code }, /* route values */
new AjaxOptions() { InsertionMode = InsertionMode.Replace,
UpdateTargetId = "productForEdit" }) %>
</td>
This does expect a ProductController with an action named GetProduct, which takes a parameter productCode. Have this action return the view "Products/Edit".
You could also pass the whole product as a parameter to the action method, but that changes nothing to the basic idea. Good luck!

Resources