ASP.NET MVC - Insert or Update view with IEnumerable model - asp.net-mvc

I've seen plenty of examples (NerdDinner, Sanderson's Sports Store, etc.) where a view is bound to a collection of objects. The syntax in the view is usually something like this...
<%# Page... Inherits="System.Web.Mvc.ViewPage<IEnumerable<MyViewModel>>" %>
...
<% foreach (var myViewModel in Model) { %>
I've also seen plenty of examples of inserts or updates where the controller automatically binds the model parameter to the form elements in the view.
I'm looking for a mix of the two techniques where my view has form elements pertaining to a collection of myViewModels where each myViewModel has 3-4 properties. The intent is to allow the user to enter a set of these in one take.
Assuming this is possible, can anyone help me with the syntax? I can't figure out how to label the form elements to make the binding work.

This is possible through the built-in model binder, but you have to do a little bit of convention-based naming of your form objects. First, your action needs to take a collection:
[HttpPost]
public ActionResult CreateFoos(List<Foo> foos)
{
// I have a list of the foo objects that were posted
}
And then, in the view, let's say you wanted to make a form for each object:
<% for (int i = 0; i < Model.Count; i++) { %>
<%: Html.TextBoxFor(x => x[i].Property1) %>
<%: Html.TextBoxFor(x => x[i].Property2) %>
<%: Html.TextBoxFor(x => x[i].Property3) %>
<% } %>
Pay attention to how the controls are rendered in the HTML, because in your "create" view, you might want to have a javascript button that allows the user to add another record, and you'll have to increase the index for each additional control. It's not too hard, but I just wanted to warn you to pay attention to the source it actually generates.

The definitive answer is here: http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
You need to name each field as if it were part of an array: "PropertyName[i]"

Related

asp.net sending a List<> of items to controller

I have the following form:
<li>
<% using (Html.BeginForm("TestMethod", "MyController", FormMethod.Post, new {id = "TestMethod"}))
{%>
<%= Html.Hidden("model", Model.MyListOfObjects) %>
<%}%>
Test
</li>
And the javascript function for the onclick is as follows:
function SubmitForm() {
document.forms["TestMethod"].submit();
}
I am trying to pass the list of objects from the view into the controller, but i have yet managed to get this to work. My Controller function is:
[Authorize]
[HttpPost]
public ActionResult TestMethod(List<Objects> model)
{
dynamic Expando = new ExpandoObject();
Expando.test = model;
return View(Expando );
}
When I view the List of objects in the debugger it always displays "System.Collections.Generic.List`1[]" with no actual objects inside.
So my question is what should I be doing to pass a List of objects into a controller?
I have also tried:
<% using (Html.BeginForm("TestMethod", "MyWork", FormMethod.Post, new {id = "TestMethod"}))
{%>
<% int itemx = 0; %>
<% foreach (var x in Model.MyListOfObjects)
{%>
<%= Html.Hidden("model"+"["+itemx+"]", x) %>
<%itemx++; %>
<% } %>
<%}%>
You cannot just put List<object> as action parameter and expect the model binder to be able to automagically guess what object types you want to put there. You will need to write a custom model binder if you wanted to handle multiple sub-types as illustrated in this post.
And if you want to use a single type for the list such as List<MyViewModel> then simply loop through each element of the list (respecting the convention) and for each element build a hidden field for each property that you want to bind.
But since those are hidden fields, I guess that the user is not supposed to modify them. In this case those hidden fields have nothing to do in your view. Let's not reinvent the ViewState that we were all so happy to get rid of when we moved to ASP.NET MVC from classic WebForms. Simply put a hidden field containing an unique id that will allow you to refetch the corresponding list elements in the POST action given this unique id from wherever you fetched them initially (your database or something I suppose).
You need to have one hidden element for each object in the list, and named model[0], model[1], etc.

ASP.Net MVC: Html.Display() for object in a Collection

The standard MVC example to draw an item with the appropriate View Template is:
Html.DisplayFor(m => m.Date)
If the Model object has a property named Date of type DateTime, this returns a string with the HTML from the Display/DateTime.ascx template.
Suppose you wanted to do the same thing, but couldn't use the strongly-typed version - you didn't know the Model's type for this View at compile time. You use the older:
Html.Display("Date");
So here's the hard part.
Suppose the Model is IEnumerable. You don't know what those objects are at compile-time, but at run-time they happen to be objects with a Date of type DateTime again, like:
public class ModelClass
{
public DateTime Date { get; set; }
}
Now suppose you want your View to iterate over those objects and render each out. If all you cared about was the value you could do this:
<%
StringBuilder sb = new StringBuilder();
foreach(object obj in (IEnumerable<object>)Model)
{
Type type = obj.GetType();
foreach(PropertyInfo prop in type.GetProperties())
{
// TODO: Draw the appropriate Display PartialView/Template instead
sb.AppendLine(prop.GetValue(obj, null).ToString());
}
}
%>
<%= sb.ToString() %>
I'm obviously taking some shortcuts to keep this example focused.
Here's the point - how do I fulfill that TODO I've written for myself? I don't just want to get the value - I want to get it nicely formatted like Html.Display("Date"). But if I just call Html.Display("Date"), it inspects the Model, which is an IEnumerable, for a property named Date, which it of course does not have. Html.Display doesn't take an object as an argument to use as the Model (like Html.Display(obj, "Date"), and all the classes and methods I can find that lie underneath appear to be internal so I can't tweak and call into them directly.
There must be some simple way to accomplish what I'm trying to do, but I can't seem to find it.
Just to make sure I'm being clear - here's an example of the code of DateTime.ascx:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<System.DateTime>" %>
<%= Model.ToString("MM/dd/yyyy") %>
And so, ideally, the output from this View that can take any Model, but in this case a list of 3 of these ModelClass objects above, would be:
11/10/2001
11/10/2002
11/10/2003
Because the code would find the Display PartialView for DateTime and render it appropriately for each.
So - how do I fulfill the TODO?
Have a look at the template code in this excellent post from Phil Haack. It seems to come close to what you are looking for: http://haacked.com/archive/2010/05/05/asp-net-mvc-tabular-display-template.aspx
I've found one potential solution to this but I'm not in love with it; it requires using several file-based templates, meaning you can't abstract this easily into a code library for use in multiple projects.
The View:
<%
StringBuilder sb = new StringBuilder();
Type itemType = Model.GetType().GetGenericArguments()[0];
sb.AppendLine("<table>");
// Pass in the Model (IEnumerable<object>)'s generic item type as
// the Model for a PartialView that draws the header
sb.Append(Html.Partial("DisplayTableHead", itemType));
foreach(object item in (IEnumerable<object>)Model)
{
sb.Append(Html.Partial("DisplayTableRow", item));
}
sb.AppendLine("</table>");
%>
<%= sb.ToString() %>
Views/Shared/DisplayTableHead.ascx:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Type>" %>
<tr>
<%
foreach (PropertyInfo prop in Model.GetProperties())
{
%>
<th><%= prop.Name %></th>
<%
}
%>
</tr>
Views/Shared/DisplayTableRow.ascx:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<tr>
<%
Type modelType = Model.GetType();
foreach (PropertyInfo modelField in modelType.GetProperties())
{
%>
<td><%= Html.Display(modelField.Name) %></td>
<%
}
%>
</tr>
But I now see the major flaw in this solution, which is that Clicktricity's posted solution acknowledges details in the ModelMetadata - like whether that particular property is set for display, whether it's complex or not, etc.

MVC - Multiple Strong Type object ...inside one view

After so many years using ASP.Net, I’m still trying to figure out how to achieve the same results using MVC.
I have a materpage with a control that is strongly type to something. When I navigate to a view of a different strongly type model ...and click on the button to execute something, I get "The model item passed into the dictionary is of type Site.Models.RegisterModel', but this dictionary requires a model item of type Site.Models.LogOnModel'".
For the sake of this example, we can take the Default MVC app that is provided with VS 2010, let’s imagine I want to change the “LogonUserControl.ascx” so that it either tells me the logged user (as it works currently) OR allow me to login from there, showing me the text boxes for username and password (therefore in this case from the home page).
So I take the control and strongly type it as:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Gioby.Models.LogOnModel>" %>
<%
if (Request.IsAuthenticated) {
%>
Welcome <b><%: Page.User.Identity.Name %></b>
[ <%: Html.ActionLink("Log Off", "LogOff", "Account")%> ]
<%
}
else {
%>
<% using (Html.BeginForm()) { %>
<div id="logon">
<div class="editor-label">
<%: Html.LabelFor(m => m.UserName)%>
<%: Html.TextBoxFor(m => m.UserName)%>
<%: Html.ValidationMessageFor(m => m.UserName, "*") %>
<%: Html.LabelFor(m => m.Password)%>
<%: Html.PasswordFor(m => m.Password)%>
<%: Html.ValidationMessageFor(m => m.Password, "*") %>
<input type="submit" value="Log On" />
</div>
<div class="editor-label">
<%: Html.ActionLink("Register here", "Register", "Account")%>
<%: Html.CheckBoxFor(m => m.RememberMe, new { #class = "pad-left" })%>
<%: Html.LabelFor(m => m.RememberMe) %>
</div>
</div>
<% } %>
<%
}
%>
Then on the HomeController, I add a procedure as:
[HttpPost]
public ActionResult Index(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
// ==>> Check Login against your DB
// Now check if param returnUrl is empty
if (!String.IsNullOrEmpty(returnUrl))
return Redirect(returnUrl);
return RedirectToAction("Index", "Home");
}
// If we got this far, something failed, redisplay form
return View(model);
}
I tested it from the home page … it works !!!
BUT when I navigate to the “Register” view (remember that the “LogonUserControl.ascx” is located inside the “MasterPage”, therefore visible from the Register view).
So when I click on the Register button, I get the error:
The model item passed into the dictionary is of type Site.Models.RegisterModel', but this dictionary requires a model item of type Site.Models.LogOnModel'.
QUESTION:
Does that mean that I will never be able to different pieces together into one view?
Let’s say I want to write an eCommerce site and on the home page I want to see “Most used Tags”, “Most bought products”, “Product of the Month”, “List of Categories” …all within the same view and each one with his own HTTP POST action.
If this is possible using MVC?
If I'm understanding the problem correctly, you have two Views that use the same MasterPage, but which are strongly typed against different ViewModels. The master page is able to include a Partial View that is also strongly typed, as long as its expected ViewModel is the same as that of the parent view. However, if you're using a view with a different ViewModel type, it doesn't know what to do.
Consider the following:
<% Html.RenderPartial("LogOn") %>
The above code implicitly includes the model data for the current view being rendered. It's exactly the same as if you had said:
<% Html.RenderPartial("LogOn", Model) %>
So this will only work if Model is a LogOnModel. Remember that the MasterPage is really a part of whatever View inherits it, so even if you're putting this in the MasterPage, it's as if you'd put the same code in every view that inherits it. So if your View's Model is not the same as the PartialView's Model, this won't work. One alternative is to use inheritance to ensure that every ViewModel will include all the information required by the Master Page. This approach is described in detail here.
But that approach means that you have to always use a factory to produce your view model, and every view model has to be somewhat aware of which master page it will use. In our product, we can use a different master page on the same view depending on what mode the user is viewing the site in, so it doesn't make sense to tie the ViewModel to that of the Master Page. We accomplish what you're describing using the RenderAction method, which allows you to render an entire controller action as if it were just a part of the larger view. Some of the advantages of this approach are discussed here.
So now you can have your MasterPage include whatever little partial views you want, but you separate the logic for building the ViewModel of each of these Views into an individual controller action that's responsible for that particular Partial View:
<% Html.RenderAction("LogOnBox") %>
The Action:
public ActionResult LogOnBox()
{
LogOnModel model = GetLogOnModel();
return PartialView("LogOnUserControl", model);
}
Now, regardless of what model your current view uses, your Master Page can include “Most used Tags”, “Most bought products”, “Product of the Month”, “List of Categories”, etc. Better still, these portions of the page can leverage output caching so they don't have to be regenerated with every page load if they don't change very often.

how to pass parameters to partial view to show db content?

I am getting my content from a database. How can i use partial views to show content on page using that database?
database table: Content
[Id, Content] these are 2 fields
i want to get the content from db using partial views.
How i will pass an id to a partial view and show the content in view page?
You could use Html.RenderAction:
public class MyController
{
[ChildActionOnly]
public ActionResult Foo(int id)
{
var content = GetContentFromDatabase(id);
return Content(content, MediaTypeNames.Text.Html);
}
}
And in your view include the partial:
<%= Html.RenderAction("foo", "mycontroller", new { id = 5 }) %>
Remark: RenderAction is part of the now released ASP.NET MVC 2 RTM. For ASP.NET MVC 1 you may take a look at the Futures assembly containing this extension method.
Inside your view, use the Html.RenderPartial function. There are a few different uses:
You can pass in a model to the partial view: <% Html.RenderPartial("partialName", model); %>
Or you can pass in a whole new ViewDataDictionary: <% Html.RenderPartial("partialName", viewData); %>
For the full documentation, see here.
EDIT: (Answer to comment):
I would include that data as part of you're view's model. For example, let's say in your model you have:
List<Person> People;
In your view, you want to loop through each one of these, and use a PartialView to display the details:
<% foreach( var p in Model.People){ %>
<p> <% Html.RenderPartial("personPartial", p); %> </p>
<%}%>
Now, your PartialView might look like:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Person>" %>
<%=Model.PersonName%>

ASP.Net MVC - Editing a collecion - is iterating through a FormCollection the best way?

This same question was asked here and an answer was given which is workable, but with the finalization of the ASP.Net MVC framework, I wondered if there was a better solution.
If I have the following class structure how do create the view page and more importantly return the data back to the controller.
public class Person {
public int Id {get;set;}
public string Name {get;set;}
public IList<TelNos> TelNos {get;set;}
}
public class TelNos{
public string Type {get;set;}
public string Number {get;set;}
}
My understanding is that within the page I would could include the following (assuming strongly typed view):
<% foreach (var telNo in Model.Product.TelNos)
{%>
<p><label for="telNo.Type">Type of Number</label>
<%= Html.TextBox("telNo.Type")%>
<%= Html.ValidationMessage("telNo.Type", "*")%>
</p>
<p><label for="telNo.Number">Type of Number</label>
<%= Html.TextBox("telNo.Number")%>
<%= Html.ValidationMessage("telNo.Number", "*")%>
</p>
<%} %>
Assuming that I initiated 2 TelNos objects I would then see 2 sets of text boxes within the view.
When that form is posted back, the suggestion on the previous post was to iterate through the FormCollection within the post method:
[AcceptVerbs( HttpVerb.POST )]
public ActionResult Whatever( FormCollection form )
{
....
}
However is that now the best approach, or have the further updates to MVC provided a better solution?
Thanks, Richard
There have not been any updates or best practices (as far as I'm aware of) to handling dynamic forms on post. The still tried and true ways are to either databind your information, or iterate through it in the FormCollection. If you take a look here it might help you with the databinding. OR in the latter case, you could iterate through the forms collection calling up the various values with their string name. Although this could possibly have some conflicts since they are all going to have the same id of
"telNo.Type"
"telNo.Number"
You might have to do some manipulation to have it be something like
"telNo.Type[i]"
"telNo.Number[i]"
where i is a number in sequence for that object in the list. You could also have it be something other string combination that generates a unique id for that object so that you can get the type and the number.
"object[i].telNo.Type"
"object[i].telNo.Number"
It really depends on how you think you can best implement it. Sometimes getting databinding to work for dynamic forms can be a pain and it's easier to just iterate through the collection, then using something like LINQ to get the ones you want/group them/etc.
Thanks for your answer. Your answer did give me an idea, which I think is different to what you are suggesting.
If I include in the view an index to the object, then the Default Model builder takes the values and assigns them to the associated objects. The view code is as follows:
<% int i=0;
foreach (var telNo in Model.Product.TelNos)
{%>
<p><label for="telNo.Type">Type of Number</label>
<%= Html.TextBox("telNo"+i.ToString()+".Type")%>
<%= Html.ValidationMessage("telNo"+i.ToString()+".Type", "*")%>
</p>
<p><label for="telNo.Number">Type of Number</label>
<%= Html.TextBox("telNo"+i.ToString()+".Number")%>
<%= Html.ValidationMessage("telNo"+i.ToString()+".Number", "*")%>
</p>
<%i++;
} %>
Richard
I like the idea at ASP.NET MVC Partial View with Form which in turn links to Model Binding To A List
I think it's better for two reason:
Going through FormCollection violates the idea of separating view form controller. Controller will have too much knowledge on how the data is displayed.
Writing a unit test will be painful. you'll need to stick all the values into the form controller manually

Resources