Post viewmodel with List<T> to controller action method - asp.net-mvc

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.

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>

ASP.NET MVC form without helper

I would like to create a form in my asp.net MVC website, without using HTML helpers like #Html.EditorFor()...
I also want to use Jquery Mobile.
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset data-role="controlgroup" data-iconpos="right">
<legend>Languages :</legend>
<input type="checkbox" name="checkbox-h-6a" id="checkbox-h-6a">
<label for="checkbox-h-6a"></label>
<input type="checkbox" name="checkbox-h-6b" id="checkbox-h-6b">
<label for="checkbox-h-6b">French</label>
<input type="checkbox" name="checkbox-h-6c" id="checkbox-h-6c">
<label for="checkbox-h-6c">German</label>
</fieldset>
}
I would like to use it with my model. (for ex. "bool UseEnglish", "bool UseFrench",..)
How can I do it simply ?
The only thing the helpers really do is abstract the name attributes of the form fields away. If you don't want to use the helpers, you will just have to make sure the name attributes are set to the right thing so that they will match up to your model after POST.
For simple properties, the name is just the property name, so given:
public string Foo { get; set; }
You'd have an input like:
<input type="text" name="Foo">
For things like complex types or reference navigation properties, you'll just chain the property names together until you're at the level you need, so given:
public Foo Foo { get; set; }
And:
public class Foo
{
public string Bar { get; set; }
}
You'll end up with:
<input type="text" name="Foo.Bar">
Finally, for list-style properties, you'll just add an index, so given:
public List<string> Foos { get; set; }
Then:
<input type="text" name="Foos[0]">
<input type="text" name="Foos[1]">
<input type="text" name="Foos[2]">
And of course, you can put all of these principles together to model any relationship:
public List<Foo> Foos { get; set; }
Then:
<input type="text" name="Foos[0].Bar">

Model binding two or more collections

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();

Create Template for complex object

I have an object like this
public class Locs
{
public string City {get; set; }
public int Zip {get; set; }
}
public class Names
{
public string FirstName {get; set; }
public string LastName {get; set; }
public Locs[] Locations {get; set; }
}
For the class Names I am generating strongly typed View based on [Create Template]. But when its generated it only show input controls for FristName and Last Name. How can I create a View that can also get Locations from the html page? So that I can easily save data from submit button.
My Form is like this
<input type="text" id="FirstName" name="FirstName" />
<input type="text" id="LastName" name="LastName" />
<p>
<input type="text" id="City1" name="City1" />
<input type="text" id="Zip1" name="Zip1" />
</p>
Add more locations
As you can see User can dynamically create City and Zip. I am now sure how many he will create. How can I get such object in my view? Can i get such object automatically? Also I want to apply validations.
If you are trying to show an editor an for each locations, you can loop through the Locations property in your view:
Rest of your view...
<% for (int i = 0; i < Model.Locations.Count; i++)
{ %>
<%= Html.TextboxFor(model => model.Locations[i].City) %>
<%= Html.TextboxFor(model => model.Locations[i].Zip) %>
}%>
Continue your view.

Basic Problem with Asp.net MVC UpdateModel(myClass)

In my Controller in a Asp.net MVC 1 app I want to use UpdateModel to populate a variable with POST data in my controller. I've looked at dozens of examples but even the most basic ones seem to fail silently for me.
Here's a very basic example that's just not working.
What am I doing wrong?
public class TestInfo
{
public string username;
public string email;
}
public class AdminController : Controller
{
public ActionResult TestSubmit()
{
var test = new TestInfo();
UpdateModel(test);//all the properties are still null after this executes
//TryUpdateModel(test); //this returns true but fields / properties all null
return Json(test);
}
}
//Form Code that generates the POST data
<form action="/Admin/TestSubmit" method="post">
<div>
<fieldset>
<legend>Account Information</legend>
<p>
<label for="username">Username:</label>
<input id="username" name="username" type="text" value="" />
</p>
<p>
<label for="email">Email:</label>
<input id="email" name="email" type="text" value="" />
</p>
<p>
<input type="submit" value="Login" />
</p>
</fieldset>
</div>
</form>
It looks like you're trying to get the controller to update the model based on the form elements. Try this instead:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult TestSubmit(TestInfo test)
{
UpdateModel(test);
return Json(test);
}
In your code, you're creating a new TestModel instead of letting the MVC runtime serialize it from the HttpPost. I've let myself get wrapped around the axel on this also, you're not the only one!
make properties of your public field:
public class TestInfo
{
public string username {get;set;}
public string email{get;set;}
}
I'm not too familiar with ASP.NET MVC, but shouldn't your TestSubmit method look more like this:
public ActionResult TestSubmit(TestInfo test)
{
UpdateModel(test);
return Json(test);
}
In the controller you should have two methods, one to respond to the GET, the other, if required is for responding to the POST.
So, firstly have a GET method:
public ActionResult Test ()
{
return View (/* add a TestInfo instance here if you're getting it from somewhere - db etc */);
}
Secondly, you'll need a POST method:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Test (TestInfo test)
{
return Json (test);
}
Notice that there's no UpdateMethod there, the ModelBinder would have done that for you.

Resources