Model binding two or more collections - asp.net-mvc

Has anyone had any luck model binding two or more collections using the code posted by Phil Haack here: Model Binding To A List?
As an example, I have the below code.
public class Book {
public string Name { get; set; }
}
public class Author {
public string Name { get; set; }
}
public ActionResult Index(List<Book> books, List<Author> authors) {
// Will never model bind two collections.
}
The HTML that I have is:
<input type="hidden" name="books.index" value="1" />
<input type="text" name="books[1].Name" />
<input type="hidden" name="books.index" value="2" />
<input type="text" name="books[2].Name" />
<input type="hidden" name="authors.index" value="1" />
<input type="text" name="authors[1].Name" />
<input type="hidden" name="authors.index" value="1" />
<input type="text" name="authors[1].Name" />
The exception that I get is:
The parameters dictionary contains an invalid entry for parameter 'authors' for method 'System.Web.Mvc.ActionResult Index(System.Collections.Generic.List1[Book], System.Collections.Generic.List1[Author])' in 'HomeController'. The dictionary contains a value of type 'System.Collections.Generic.List1[Book]', but the parameter requires a value of type 'System.Collections.Generic.List1[Author]'. Parameter name: parameters
Am I doing something wrong or is this not supported by ASP.NET MVC?

Your problem is somewhere else, I was unable to reproduce. The following works fine for me:
Model:
public class Book
{
public string Name { get; set; }
}
public class Author
{
public string Name { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(List<Book> books, List<Author> authors)
{
return View();
}
}
View:
<% using (Html.BeginForm()) { %>
<input type="text" name="books[0].Name" value="book 1" />
<input type="text" name="books[1].Name" value="book 2" />
<input type="text" name="authors[0].Name" value="author 1" />
<input type="text" name="authors[1].Name" value="author 2" />
<input type="submit" value="OK" />
<% } %>
It successfully binds values back in the POST action.
UPDATE:
I confirm that this is a bug in ASP.NET MVC 3 RC2 which will be fixed in the RTM. As a workaround you could put the following in your Application_Start:
ModelMetadataProviders.Current = new DataAnnotationsModelMetadataProvider();

Related

Why is data not transmitted to the controller?

I have next form:
<form asp-controller="Chat" asp-action="AddFile" method="post" asp-route-chatId="#Model.ChatId" enctype="multipart/form-data">
<textarea id="messageInput" class="textInput" style="width: 80vh" name="messageInput"></textarea>
<div>
<input type="submit" id="sendButton" value="Send Message" />
<input type="file" class="inputfile " id="File" name="File" value="File"/>
<label for="File">Choose a file</label>
</div>
</form>
ViewModel
public class ChatFileViewModel
{
public long ChatId { get; set; }
public string messageInput { get; set; }
public IFormFile File { get; set; }
}
and post method:
[HttpPost]
public void AddFile([FromBody] ChatFileViewModel chatFile)
{ ... }
The issue is - every time i press submit it transfers ChatId correctly, while messageInput and File are null. I have no idea what it is, because i have exactly the same construction working correctly in the other part of my app.
Using [FromBody] To force Web API to read a simple type from the request body, but your object is complex contain string and int can not treat as simple type.
Remove FormBody, I reproduce and it worked
More about FormBody at https://learn.microsoft.com/en-us/aspnet/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api
You should remove the [FromBody] attribute, since your request data is in the form. [FromBody] is usually used to deal with data in the request body like JSON and XML
[HttpPost]
public void AddFile(ChatFileViewModel chatFile)
{ ... }
For more details, you can refer to Model binding.
if you need of want to specify binding then you can use [FromForm] because you correctly setup for into multipart/form-data but it depends on your other actions
public IActionResult AddFile([FromForm]ChatFileViewModel model)
if you use asp- tag helpers you can use them for controls as well
<form asp-controller="Home" asp-action="AddFile" method="post" asp-route-chatId="#Model.ChatId" enctype="multipart/form-data">
<textarea asp-for="messageInput" class="textInput" style="width: 80vh" name="messageInput"></textarea>
<div>
<input type="submit" id="sendButton" value="Send Message" />
<input type="file" class="inputfile " asp-for="File" />
<label asp-for="File">Choose a file</label>
</div>
</form>

.net MVC posting nested model to controller, data set is empty

<form method="post">
<input type="text" name="ClientList[0].ItemList[0].ItemID">
<input type="text" name="ClientList[0].ItemList[0].Qty">
<input type="text" name="ClientList[0].ItemList[1].ItemID">
<input type="text" name="ClientList[0].ItemList[1].Qty">
<input type="text" name="ClientList[1].ItemList[0].ItemID">
<input type="text" name="ClientList[1].ItemList[0].Qty">
<input type="text" name="ClientList[1].ItemList[1].ItemID">
<input type="text" name="ClientList[1].ItemList[1].Qty">
<input type="submit" />
</form>
This is controller
[HttpPost]
public ActionResult Sale(List<ClientList> ClientList)
{
return View();
}
public class ClientList
{
public List<ItemList> ItemList = new List<ItemList>();
}
public class ItemList
{
public int ItemID { get; set; }
public string Qty { get; set; }
}
This is result
the second loop, items details are not submitting.
Your ItemList is a field, not a property, and the DefaultModelBinder cannot set its value.
Change it to
public class ClientList
{
public List<ItemList> ItemList { get; set; }
}
As a side note, the name attributes can be just
<input type="text" name="[0].ItemList[0].ItemID">
which allows you to name the parameter in the POST method to anything you want (except ItemList)
I used a bit different approach, with named list
[HttpPost]
public ActionResult Sale(List<ClientList> ClientList)
{
return View();
}

Pass Object from view to controller using mvc4

View
#if (weekMaster != null)
{
using (Html.BeginForm("UpdatePlan", "generalPlan", FormMethod.Post, new { }))
{
<table class="table-bordered">
<tr>
#foreach (TermMaster obj in weekMaster.ToList())
{
<td align="center">
<span> #obj.termStartDate.ToString("dd MMM") - #obj.termEndDate.ToString("dd MMM")</span>
<br />
<input type="hidden" name="ObjHid" value="#obj" />
<input type="hidden" name="startDate" value="#obj.termStartDate" />
<input type="hidden" name="endDate" value="#obj.termEndDate" />
<input type="text" style="width:80%" name="weekSession" />
</td>
}
<td>
<input type="submit" value="Update" class="btn-primary" />
</td>
</tr>
</table>
} }
Controller
[HttpPost]
public ActionResult UpdatePlan(List<DateTime> startDate, List<DateTime> endDate, List<int> weekSession, List<TermMaster> ObjHid)
{
return View();
}
I am trying pass Class Object from View to Controller above TermMaster class Object pass using input method <input type="hidden" name="ObjHid" value="#obj" /> but showing NULL value if pass single value like startDate and endDate then it work fine.
What is wrong in my code? how to pass class object List in Post Method?
Please refer Image
You can not bind objects to controller from your input. You can serialize object to json. In the controller you can take your inputs value as string and deserialize it.
You have to do it by below approach.
Create a model instead of multiple parameters, and use index in cshtml.
public class model
{
public List<DateTime> startDate { get; set; }
public List<DateTime> endDate { get; set; }
public List<int> weekSession { get; set; }
public List<TermMaster> ObjHid { get; set; }
}
CSHTML
#{ int i = 0; }
#foreach (TermMaster obj in weekMaster.ToList())
{
<td align="center">
<span> #obj.termStartDate.ToString("dd MMM") - #obj.termEndDate.ToString("dd MMM")</span>
<br />
<input type="hidden" name="ObjHid[#i].termStartDate" value="#obj.termStartDate.ToString("dd MMM")" />
<input type="hidden" name="ObjHid[#i].termStartDate" value="#obj.termStartDate.ToString("dd MMM")" />
<input type="hidden" name="startDate[#i]" value="#obj.termStartDate" />
<input type="hidden" name="endDate[#i]" value="#obj.termEndDate" />
<input type="text" style="width:80%" name="weekSession[#i]" />
</td>
i++
}

Post viewmodel with List<T> to controller action method

I know there are a lot of posts out there regarding this, most of which I have read and tried all morning but still can't get it working.
I have a view model as such:
namespace GrantTracker.ViewModels
{
public class CoverPageViewModel
{
public List<Compliance> Compliances { get; set; }
}
}
I have a partial view that uses the view model:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<GrantTracker.ViewModels.CoverPageViewModel>" %>
<%
for (var i = 0; i < Model.Compliances.Count; i++)
{ %>
<%=Html.HiddenFor(x => x.Compliances[i].ComplianceId) %>
<%=Html.TextBoxFor(x => x.Compliances[i].ComplianceName) %>
<% } %>
This properly displays the text boxes and their values:
The generated source looks ok to me:
<input id="Compliances_0__ComplianceId" name="Compliances[0].ComplianceId" type="hidden" value="1" />
<input id="Compliances_0__ComplianceName" name="Compliances[0].ComplianceName" type="text" value="Human Subjects" />
<input id="Compliances_1__ComplianceId" name="Compliances[1].ComplianceId" type="hidden" value="2" />
<input id="Compliances_1__ComplianceName" name="Compliances[1].ComplianceName" type="text" value="Vertebrate Animals" />
<input id="Compliances_2__ComplianceId" name="Compliances[2].ComplianceId" type="hidden" value="3" />
<input id="Compliances_2__ComplianceName" name="Compliances[2].ComplianceName" type="text" value="Hazardous Substances" />
When I submit the page the textbox properties are as far as I can tell properly posted:
Compliances[0].ComplianceId:1
Compliances[0].ComplianceName:Human Subjects
Compliances[1].ComplianceId:2
Compliances[1].ComplianceName:Vertebrate Animals
Compliances[2].ComplianceId:3
Compliances[2].ComplianceName:Hazardous Substances
However, the values are all gone when accessed in the controller action
[HttpPost]
public ActionResult SaveCoverPage(CoverPageViewModel coverPageViewModel)
{
return Content(coverPageViewModel.Compliances[0].ComplianceId.ToString());
}
The debugger shows that it knows it should contain three Compliance objects.
However, when drilled down none of them have their values:
Any help with this would be greatly appreciated. I am really stuck on this.
Change your Compliance class as below.
public class Compliance
{
public int ComplianceId { get; set; }
public string ComplianceName { get; set; }
}
You should define properties correctly.

Accepting params or raw data in controller?

I was wondering if it would be possible having a "params" argument in a controller function, or something similar which would allow me to process X amount of entries in my form.
For instance, I have a form which has X amount of "name" elements, which are auto-generated through jQuery. An example of these name elements could be the following:
<input type="text" name="studentName1"></input>
<input type="text" name="studentName2"></input>
<input type="text" name="studentName3"></input>
Now, there's a different amount of student names every time, so this makes it quite complex for me to handle the form data in my controller. I had something like the following 2 examples in mind, but of course they wouldn't work in reality.
[HttpPost]
public ActionResult PostStudentNames(params string[] studentNames)
Or:
[HttpPost]
public ActionResult PostStudentNames(string[] formValues)
Can I achieve something similar to that?
I just want to chime in with a different approach you can use for this. If it's more convenient, you can model bind directly to collections of primitive or complex types. Here's 2 examples:
index.cshtml:
#using (Html.BeginForm("ListStrings", "Home"))
{
<p>Bind a collection of strings:</p>
<input type="text" name="[0]" value="The quick" /><br />
<input type="text" name="[1]" value="brown fox" /><br />
<input type="text" name="[2]" value="jumped over" /><br />
<input type="text" name="[3]" value="the donkey" /><br />
<input type="submit" value="List" />
}
#using (Html.BeginForm("ListComplexModel", "Home"))
{
<p>Bind a collection of complex models:</p>
<input type="text" name="[0].Id" value="1" /><br />
<input type="text" name="[0].Name" value="Bob" /><br />
<input type="text" name="[1].Id" value="2" /><br />
<input type="text" name="[1].Name" value="Jane" /><br />
<input type="submit" value="List" />
}
Student.cs:
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
}
HomeController.cs:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult ListStrings(List<string> items)
{
return View(items);
}
public ActionResult ListComplexModel(List<Student> items)
{
return View(items);
}
}
ListStrings.cshtml:
#foreach (var item in Model)
{
<p>#item</p>
}
ListComplexModel.cshtml:
#foreach (var item in Model)
{
<p>#item.Id. #item.Name</p>
}
The first form simply binds a list of strings. The second, binds the form data to a List<Student>. By using this approach, you can let the default model binder do some of the tedious work for you.
Updated for comment
Yes you can do that too:
Form:
#using (Html.BeginForm("ListComplexModel", "Home"))
{
<p>Bind a collection of complex models:</p>
<input type="text" name="[0].Id" value="1" /><br />
<input type="text" name="[0].Name" value="Bob" /><br />
<input type="text" name="[1].Id" value="2" /><br />
<input type="text" name="[1].Name" value="Jane" /><br />
<input type="text" name="ClassId" value="13" /><br />
<input type="submit" value="List" />
}
Controller action:
public ActionResult ListComplexModel(List<Student> items, int ClassId)
{
// do stuff
}
Mathias,
This works perfectly well without recourse to the params object. your form controls:
<input type="text" name="studentName" />
<input type="text" name="studentName" />
<input type="text" name="studentName" />
<input type="text" name="professorName" />
You would use the FormCollection object, which will contain all your form elements as either comma separated lists (if a control array) or as single properties. In the above example, this is what we'd get:
[HttpPost]
public ActionResult PostStudentNames(FormCollection formValues)
{
// basic check for rogue commas inside input controls
// would need far more sophistication in a #real# app :)
var valueStudents = formValues["studentName"].Split(',')
.Where(x => x.Length > 0).ToArray();
var valueProfessor = formValues["professorName"];
// other stuff
}
etc... At least, this is my recollection of this from a recent project. :)
<input type="text" name="studentName[0]"></input>
<input type="text" name="studentName[1]"></input>
<input type="text" name="studentName[2]"></input>
public ActionResult PostStudentNames(string[] studentName)
{
}

Resources