can default model binder generate IEnumerable<FormItem>? - asp.net-mvc

server:
public class FormItems
{
public IEnumerable<MyClass> Values { get; set; }
}
client:
<form id="myform" action="/" method="post">
<!-- Those inputs could be added dynamically -->
<input type="text" name="[0].Value" />
<input type="text" name="[1].Value" />
<input type="text" name="[2].Value" />
<input type="text" name="[3].Value" />
<button type="submit">OK</button>
</form>
and finally AJAXify the form:
$(function() {
$('#myform').submit(function() {
var form = $(this);
$.ajax({
url: form.attr('action'),
type: form.attr('method'),
data: form.serialize(),
success: function(result) {
}
});
});
});
How can I use default model binder get the ajax data into strongly typed IEnumerable?
[HttpPost]
public JsonResult Save(FormItems data)

Assuming MyClass is something like this
public class MyClass
{
public string Value { get; set; }
}
Your html should look like this (note that name of each value input is prefixed by matching enumerable property name in FormItems)
< form id="myform" action="/" method="post">
<!-- Those inputs could be added dynamically -->
<input type="text" name="Values[0].Value" />
<input type="text" name="Values[1].Value" />
<input type="text" name="Values[2].Value" />
<input type="text" name="Values[3].Value" />
<button type="submit">OK</button>
</form>

Related

Asp.Net MVC Core built-in tag helpers won't process attributes provided from custom taghelpers

Out of boredom of writing the same boilerplate forms, I thought I'd rather write a tag helper that would produce attributes that can be processed from stock tag helpers. But, even though I managed to get my tag helper before work before form tag helpers, form tag helpers won't process the ones that I produce.
Here is the cshtml:
#model City
#{
Layout = "_Layout";
ViewData["Title"] = "Create City";
}
<form method="post" asp-action="Create">
#foreach (string propName in BaseModel.GetProperties<City>()) {
<formgroup for="#propName" />
}
<div class="form-group">
<label asp-for="Name">Name:</label>
<input class="form-control" asp-for="Name" />
</div>
<div class="form-group">
<label asp-for="Country">Country:</label>
<input class="form-control" asp-for="Country" />
</div>
<div class="form-group">
<label asp-for="Population">Population:</label>
<input class="form-control" asp-for="Population" />
</div>
<button type="submit" class="btn btn-primary">Create</button>
<a class="btn btn-secondary" asp-controller="Home" asp-action="Index">Cancel</a>
</form>
Here is the output:
<form method="post" action="/City/Create">
<div class="form-group"><label asp-for="Name"></label><input asp-for="Name" class="form-control"></div>
<div class="form-group"><label asp-for="Country"></label><input asp-for="Country" class="form-control"></div>
<div class="form-group"><label asp-for="Population"></label><input asp-for="Population" class="form-control"></div>
<div class="form-group">
<label for="Name">Name:</label>
<input class="form-control" type="text" id="Name" name="Name" value="">
</div>
<div class="form-group">
<label for="Country">Country:</label>
<input class="form-control" type="text" id="Country" name="Country" value="">
</div>
<div class="form-group">
<label for="Population">Population:</label>
<input class="form-control" type="number" data-val="true" data-val-required="The Population field is required." id="Population" name="Population" value="">
</div>
<button type="submit" class="btn btn-primary">Create</button>
<a class="btn btn-secondary" href="/">Cancel</a>
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8M_6usK6CRRNkwluTiTW8uaAAfhMcU9tAxyCT7z55zQKmpUwpi_lfSDIN4FrlMo9cE3Ka9zgX4WdpXHUdlBFVGsLIw7h_cR3FjJb6Vjqnjm8mQmtKTey_9l188p9E2sKgiksO_OB6K9-F1D7SP2lX0g"></form>
Here is my tag helper:
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
namespace ViewComponents.Infrastructure.TagHelpers {
[HtmlTargetElement("formgroup", Attributes = "for", TagStructure = TagStructure.NormalOrSelfClosing)]
public class FormGroupTagHelper : TagHelper {
/// <inheritdoc />
public override int Order => -2000;
[HtmlAttributeNotBound]
[ViewContext]
public ViewContext ViewContext { get; set; }
protected IHtmlGenerator Generator { get; }
public string For { get; set; }
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) {
if (context == null) {
throw new ArgumentNullException(nameof(context));
}
if (output == null) {
throw new ArgumentNullException(nameof(output));
}
// So that other tag helpers are processed after me...
var childContent = await output.GetChildContentAsync();
// Starting up...
// Replace the tag name, include the form-group bootstrap class.
output.TagName = "div";
output.TagMode = TagMode.StartTagAndEndTag;
output.Attributes.Add("class", "form-group");
PropertyInfo propInfo = ViewContext.ViewData.ModelMetadata.ModelType.GetTypeInfo().GetDeclaredProperty(For);
output.Content.AppendHtml(GenerateLabel(new Dictionary<string, string> { ["asp-for"] = propInfo.Name }));
output.Content.AppendHtml(GenerateInput(new Dictionary<string, string> { ["asp-for"] = propInfo.Name, ["class"] = "form-control" }));
}
public static IHtmlContent GenerateLabel(IReadOnlyDictionary<string, string> attrDict) {
TagBuilder tBuilder = new TagBuilder("label");
foreach (var kvp in attrDict)
tBuilder.Attributes.Add(kvp);
return tBuilder;
}
public static IHtmlContent GenerateInput(IReadOnlyDictionary<string, string> attrDict) {
TagBuilder tBuilder = new TagBuilder("input");
foreach (var kvp in attrDict)
tBuilder.Attributes.Add(kvp);
return tBuilder;
}
}
}
Any help will be appreciated.
Have a nice one!
Tag Helpers aren't processed in a specific order, so you cannot count on one being able to do something based on the output of the other. Each tag helper needs to be designed to work independently of any other, so if you need to render HTML, you'll need to handle that yourself in your tag helper. However, since you're trying to have one tag generate any kind of form field, that's going to be a heavy lift. This is basically beyond the scope of what a tag helper is intended for.

Passing object from AngularJS to ASP.NET MVC Controller

I'm trying to pass some data to my MVC controller from AngularJS but the Customer object is always null on the MVC controller. What am I missing?
Angular
$scope.new = {};
$scope.AddCustomer = function () {
$http.post('/Customer/SaveCustomer', $scope.new).success(function () {
});
}
HTML
<input class="form-control" type="text" placeholder="CustomerID" ng-model="new.c.CustomerID" />
<input class="form-control" type="text" placeholder="CompanyName" ng-model="new.c.CompanyName" />
<button type="button" class="btn btn-primary" ng-click="AddCustomer()">Save</button>
C#
[HttpPost]
public void SaveCustomer(Customer customer)
{
....
}
public class Customer
{
public string CustomerID { get; set; }
public string CompanyName { get; set; }
}
Update your HTML like this :
Change new.c.CustomerID to new.CustomerID
<input class="form-control" type="text" placeholder="CustomerID" ng-model="new.CustomerID" />
<input class="form-control" type="text" placeholder="CompanyName" ng-model="new.CompanyName" />
Now this will work
$http.post('/Customer/SaveCustomer', $scope.new).success(function () {
});
First mind the camel case in javascript object
$scope.new = {
customerID: '',
companyName: ''
};
$scope.AddCustomer = function() {
$http.post('/Customer/SaveCustomer', $scope.new).success(function() {});
<!--check the changes in ng-model-->
<input class="form-control" type="text" placeholder="CustomerID" ng-model="new.customerID" />
<input class="form-control" type="text" placeholder="CompanyName" ng-model="new.companyName" />
<button type="button" class="btn btn-primary" ng-click="AddCustomer()">Save</button>
<!--Best of Luck-->
Your model seems to be
new = {
c : {
CustomerID : '',
CompanyName : ''
}
}
Which doesn't map to your Customer class. You should refer to new.CustomerID and new.CompanyName. Futhermore I would avoid using the new keyword as variable name.

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)
{
}

how to Post list using ajax

My problem here is how to send the list values to the controller upon an ajax post. With my actual code, the list is null on post.
My view:
#using (Html.BeginForm("UsersList", "Project", FormMethod.Post, new { id = "Users" }))
{
<div id="MyList" >
<table>
<thead>
<tr><th></th><th>User</th><th>End date</th></tr>
</thead>
#Html.EditorFor(x => x.GetUsers)
</table>
</div>
<script type="text/javascript">
$(function () {
$('#MyList).dialog({
autoOpen: false,
width: 820,
buttons: {
"Save": function () {
$("#Users").submit();
},
"Cancel": function () {
$(this).dialog("close");
}
}
});
</script>
Model:
public class ProjectModel
{
public List<ProjectList>GetUsers{get;set;}
}
public class ProjectList
{
public bool Selected { get; set; }
public int UserID { get; set; }
}
My controller:
[HttpPost]
public ActionResult UsersList(ProjectModel model)
{
return View(model);
}
Sending a list can be difficult. I think MVC 3.0 has (in-built) support for sending JSON to the controller which would make this easier.
However with prior versions I think this article should be what your looking for:
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
Effectively you give everything in the list the same name in your HTML (as the name will be what gets sent in the key/value string that gets sent and it will bind as a list.
For more complex items you name each object property but add an index to show which object they get attached too:
<form method="post" action="/Home/Create">
<input type="text" name="[0].Title" value="Curious George" />
<input type="text" name="[0].Author" value="H.A. Rey" />
<input type="text" name="[0].DatePublished" value="2/23/1973" />
<input type="text" name="[1].Title" value="Code Complete" />
<input type="text" name="[1].Author" value="Steve McConnell" />
<input type="text" name="[1].DatePublished" value="6/9/2004" />
<input type="text" name="[2].Title" value="The Two Towers" />
<input type="text" name="[2].Author" value="JRR Tolkien" />
<input type="text" name="[2].DatePublished" value="6/1/2005" />
<input type="submit" />

model binding of non-sequential arrays

I am having a table in which i`m dynamically creating and deleting rows. How can I change the code such that the rows be added and deleted and the model info property filled accordingly.
Bearing in mind that the rows can be dynamically created and deleted, I may have Info[0], Inf0[3], info[4]... My objective is to be able to bind the array even if it`s not in sequence.
Model
public class Person
{
public int[] Size { get; set; }
public string[] Name { get; set; }
public Info[]info { get; set; }
}
public class Info
{
public string Address { get; set; }
public string Tel { get; set; }
View
<script type="text/javascript" language="javascript">
$(function () {
var count = 1;
$('#AddSize').live('click', function () {
$("#divSize").append('</br><input type="text" id="Size" name="Size" value=""/><input type = "button" id="AddSize" value="Add"/>');
});
$('#AddName').live('click', function () {
$("#divName").append('</br><input type="text" id="Name" name="Name" value=""/><input type = "button" id="AddName" value="Add"/>');
});
$('#AddRow').live('click', function () {
$('#details').append('<tr><td>Address</td><td> <input type="text" name="Info[' + count + '].Address"/></td><td>Tel</td><td><input type="text" name="Info[' + count++ + '].Tel"/></td> <td><input type="button" id="AddRow" value="Add"/> </td></tr>');
});
});
</script>
</head>
<body>
<form id="closeForm" action="<%=Url.Action("Create",new{Action="Create"}) %>" method="post" enctype="multipart/form-data">
<div id="divSize">
<input type="text" name="Size" value=""/> <input type="button" value="Add" id="AddSize" />
</div>
<div id="divName">
<input type="text" name="Name" value=""/> <input type="button" value="Add" id="AddName" />
</div>
<div id="Tab">
<table id="details">
<tr><td>Address</td><td> <input type="text" name="Info[0].Address"/></td><td>Tel</td><td><input type="text" name="Info[0].Tel"/></td> <td><input type="button" id="AddRow" value="Add"/> </td></tr>
</table>
</div>
<input type="submit" value="Submit" />
</form>
</body>
}
Controller
public ActionResult Create(Person person)
{
return new EmptyResult();
}
Here's a nice blog post that you might find helpful.

Resources