I would like to post a request via AJAX to a Controller with a model prefix. I need a prefix as I have two forms on one page with similiar model properties ("asp-for" is generating similiar IDs and Names). I'm using .NET Core 3.1.
Request post works fine without a prefix. When I'm using a prefix like in the example below, passed model is null in Controller:
Controller with prefix
[HttpPost]
public async Task<IActionResult> Save([Bind(Prefix="ShipmentAddress"), FromBody]ShipToAddressViewModel model)
{
// model is null
...
return PartialView(ShipmentAdressFormView, model);
}
View with prefix
In my View I set the HTMLFieldPrefix as well:
#model ShipToAddressViewModel
#{
ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "ShipmentAddress";
}
...
$("body").on('submit','#formShipmentAddress', function (e) {
// Getting the data (see passed JSON below)
var formData = new FormData(<HTMLFormElement>document.getElementById('formShipmentAddress'));
var object = {};
formData.forEach(function (value, key) {
object[key] = value;
});
var data = object;
$.ajax({
type: "POST",
url: url,
data: JSON.stringify(data),
contentType: "application/json; charset=utf-8",
success: (data) => {
success(data);
}
});
});
Passed JSON payload with prefix
{"ShipmentAddress.ID":"3","ShipmentAddress.Name":"Eddard Stark","ShipmentAddress.Name2":"c/o Ned",..."}
Model
public class ShipToAddressViewModel
{
public int ID { get; set; }
[Display(Name="Name")]
public string Name { get; set; }
[Display(Name = "Name 2")]
public string Name2 { get; set; }
...
}
UPDATE
If I remove the prefix from keys of my objects, then it works, though more like a work around (Model binding starts by looking through the sources for the key ShipmentAddress.ID. If that isn't found, it looks for ID without a prefix.):
// Getting the data (see passed JSON below)
var formData = new FormData(<HTMLFormElement>document.getElementById('formShipmentAddress'));
var object = {};
formData.forEach(function (value, key) {
object[key.replace("ShipmentAddress.","")] = value;
});
var data = object;
For Asp.Net Core, there are two ways to bind the model, ModelBinding and JsonInputFormatter. For sending request with json, it will use JsonInputFormatter. Bind will not work with JsonInputFormatter.
Here is a working demo like below:
1.View:
#{
ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "ShipmentAddress";
}
#model ShipToAddressViewModel
<form id="formShipmentAddress">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="ID" class="control-label"></label>
<input class="form-control" asp-for="ID">
<span asp-validation-for="ID" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input class="form-control" asp-for="Name">
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Name2" class="control-label"></label>
<input class="form-control" asp-for="Name2">
<span asp-validation-for="Name2" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
#section Scripts
{
<script>
$("body").on('submit', '#formShipmentAddress', function (e) {
e.preventDefault();
var id = parseInt($("#ShipmentAddress_ID").val());
var name = $("#ShipmentAddress_Name").val();
var name2 = $("#ShipmentAddress_Name2").val();
var data = {
ID: id,
Name: name,
Name2: name2
};
$.ajax({
type: "POST",
url: "/Home/Save",
data: JSON.stringify(data),
contentType: "application/json; charset=utf-8",
success: (data) => {
success(data);
}
});
});
</script>
}
2.Controller:
public async Task<IActionResult> Save([FromBody]ShipToAddressViewModel model)
{
//do your stuff...
}
3.Result:
Related
I'm trying to post a nested viewmodel which has a List<IFormFile> using a jquery AJAX post, however the IFormFile property is always null on the model.
Here is my nested Model (the IFormFile is inside the ChildModel):
public class ParentViewModel
{
public string ParentName{ get; set; }
public ChildModel Child{ get; set; }
}
ChildModel class:
public class ChildModel
{
public string ChildName{ get; set; }
public IList<IFormFile> Images{ get; set; }
}
Controller Method:
[HttpPost]
public async Task<bool> CompleteAppointment(ParentViewModel viewModel)
{
// save logic
return true;
}
View (this is a partial view. No form here, need to pass documents on ajax call):
#model SomeOtherViewModel
<div class="row">
//controls using SomeOtherViewModel
</div>
<div class="row">
<div class="col-md-6">
<div>
<span class="font-weight-bold">Child Name</span><br />
<div class="pt-2 pb-2"><input id="name" type="text" class="form-control" /></div>
<input type="file" id="images" multiple />
</div>
</div>
</div>
JavaScript Code:
function save(){
var formData = new FormData();
formData.append("ParentName", "Anne");
formData.append("Child[ChildName]", "Sam");
var files = $("#images").get(0).files;
for (var i= 0; i< files.length; i++) {
formData.append("Child[Images]", files[i]);
}
$.ajax({
url: "FamilyController/Save",
type: "POST",
data: formData,
processData: false,
contentType: false,
success: function (data) {}
);
}
viewModel.ChildModel.Images is always null. I have tried formData.append("Child[Images][i]", files[i]);, Adding IFormFile to a wrapper class then use it in the child, and few other options. But none works.
However, the wired thing is if I add public IList<IFormFile> Images{ get; set; } to ParentViewModel and append as formData.append("Images", files[i]); , files become available in controller.
What am I missing here? I really appreciate any help.
Here is a demo worked:
Controller:
public IActionResult ModalPartial() {
return View();
}
public IActionResult Save(ParentViewModel pdata) {
return View();
}
ModalPartial.cshtml:
<div class="row">
<div class="col-md-6">
<div>
<span class="font-weight-bold">Child Name</span><br />
<div class="pt-2 pb-2"><input id="name" type="text" class="form-control" /></div>
<input type="file" id="images" multiple />
</div>
<button onclick="save()">save</button>
</div>
</div>
#section scripts{
<script type="text/javascript">
function save(){
var pdata = new FormData();
pdata.append('ParentName', 'Anne');
pdata.append('Child.ChildName', 'Sam');
var files = $("#images").get(0).files;
for (var i = 0; i < files.length; i++) {
pdata.append('Child.Images', files[i]);
}
$.ajax({
url: "Save",
type: "POST",
data: pdata ,
processData: false,
contentType: false,
success: function (data) { }
});
}
</script>
}
result:
Good Day,
I am at my wits end and I know that it's probably something simple I am missing. After searching and searching I can't seem to find a solution that works for me. My problem is that I am trying to pass information from input fields via AJAX to a controller using JSON.
This is the code for my C# Model
public partial class EgInput
{
public string DisplayId { get; set; }
public string EgUid { get; set; }
public double Quantity { get; set; }
public string Quality { get; set; }
public string Required { get; set; }
public string MaxAmount{ get; set; }
}
This is the Code for my C# Controller Action
public String SaveTest(EgInput obj)
{
Debug.WriteLine("Uid Value: "+ obj.DisplayId);
return (obj.DisplayId);
}
Below is the Input Fields
<input asp-for="DisplayId" class="form-control" />
<span asp-validation-for="DisplayId" class="text-danger"></span>
<input asp-for="EgUid" class="form-control" />
<span asp-validation-for="EgUid" class="text-danger"></span>
<table>
<tr id="eg_row">
<td>
<input asp-for="Quantity" class="form-control" />
<span asp-validation-for="Quantity" class="text-danger"></span>
</td>
<td>
<input asp-for="Quality" class="form-control" />
<span asp-validation-for="Quality" class="text-danger"></span>
</td>
<td>
<input asp-for="Required" class="form-control" />
<span asp-validation-for="Required" class="text-danger"></span>
</td>
<td>
<input asp-for="MaxAmount" class="form-control" />
<span asp-validation-for="MaxAmount" class="text-danger"></span>
</td>
</tr>
</table>
<button type="button" id="save_test">Save</button>
This is the JavaScript I'm Executing.
$('#save_test').on("click", function () {
$("tr").each(function () {
if ($(this).is("#eg_row")) {
var did = $("#DisplayId").val();
var eid = $("#EgUid").val();
var quantity = $(this).find("#Quantity").val();
var quality = $(this).find("#Quality").val();
var req = $(this).find("#Required").val();
var max = $(this).find("#MaxAmount").val();
var obj = JSON.stringify({
"DisplayId": did,
"EgUid": eid,
"Quantity": quantity,
"Quality": quality,
"Required": req,
"MaxAmount": max
});
console.log("clicked");
$.ajax({
type: "POST",
dataType: "json",
data: obj,
contentType: 'application/json',
url: '/Index/SaveTest',
success: function (msg) {
alert(msg);
}
});
}
});
});
I have Console logged the JSON array to see that their are indeed values pulled from the fields. I have tested that the correct action in the controller is called and changed its return to a message to know that it was successfully processed. Using the is() function in C# it says that the object is indeed to the type. However, the values are always null when using Debug Console to verify the information in the object. Thanks in advance for any assistance that is provided.
Like I said, it was a rather simple solution to my problem. For some reason the JSON stringify wasn't being passed. I tried using the [FromBody] attribute or the [FromUri] but the object still remained null. So instead I tried just simply passing the Javascript object directly, and did not use json and the data transferred.
Revised JavaScript Code Below
$('#save_test').on("click", function () {
$("tr").each(function () {
if ($(this).is("#eg_row")) {
var did = $("#DisplayId").val();
var eid = $("#EgUid").val();
var quantity = $(this).find("#Quantity").val();
var quality = $(this).find("#Quality").val();
var req = $(this).find("#Required").val();
var max = $(this).find("#MaxAmount").val();
var obj = {
"DisplayId": did,
"EgUid": eid,
"Quantity": quantity,
"Quality": quality,
"Required": req,
"MaxAmount": max
};
console.log("clicked");
$.ajax({
type: "POST",
dataType: "html",
data: obj,
contentType: 'application/x-www-form-urlencoded',
url: '/Index/SaveTest',
success: function (msg) {
alert(msg);
}
});
}
});
});
I am giving users the ability to add images for a retail product inside of an Add/Edit Product modal.
Modal ViewModel:
public class ProductModalViewModel
{
public ProductModalViewModel()
{
Product = new ProductDTO();
Images = new List<ProductImageViewModel>();
}
public ProductDTO Product { get; set; }
public List<ProductImageViewModel> Images { get; set; }
}
Each product image is contained in its own ViewModel, as below:
Image ViewModel:
public class ProductImageViewModel
{
public ProductImageViewModel()
{
ProductImage = new ProductImageDTO();
}
public ProductImageDTO ProductImage { get; set; }
[DataType(DataType.Upload)]
public IFormFile ImageFile { get; set; }
}
Upon submitting the form to save the product (and any added images) my request gets hung up and displays "pending" in Chrome Development Tools. My controller action is never reached.
This only happens when I include the ProductImage Fields/ViewModel/Logic in my project. This was not occurring before adding that functionality to the modal, and works fine if I remove all of the ProductImage Fields/ViewModel/Logic then submit again.
Is there something inherently wrong with including my IFormFile inside of a nested ViewModel? Or could this be something else. The rest of my relevent code is below.
Controller:
[HttpPost]
public IActionResult SaveProduct([FromForm]ProductModalViewModel model)
{
//save code
}
View (Modal):
<form id="formSaveProduct" onsubmit="SaveProduct(event)" enctype="multipart/form-data">
//Other product fields removed for brevity
<div class="row">
<div class="col-md-12">
<ul class="list-group" id="image-list-group">
#for (int i = 0; i < Model.Images.Count(); i++)
{
<partial name="_ImageListItem" for="Images[i]" />
}
</ul>
</div>
</div>
</form>
PartialView (ProductImage):
<li class="list-group-item my-1">
<input type="hidden" asp-for="ProductImage.Id" class="image-id" />
<input type="hidden" asp-for="ProductImage.ProductId" class="image-productid" />
<div class="row">
<div class="col-3">
<input type="text" readonly asp-for="ProductImage.Order" class="image-order" />
</div>
<div class="col-6">
<img src="..\#Model.ProductImage.Path" class="image-display" />
</div>
</div>
</li>
Script:
function SaveProduct(e) {
e.preventDefault(); // prevent standard form submission
$.ajax({
url: "#Url.Action("SaveProduct", "ProductManagement", new { Area = "Admin" })",
method: "post",
data: new FormData($('#formSaveProduct')[0]),
contentType: false,
processData: false,
cache: false,
success: function (result) {
//removed for brevity
}
});
}
First you dont need this
[DataType(DataType.Upload)]
public IFormFile ImageFile { get; set; }
so you can change your code to
public IFormFile ImageFile { get; set; }
And in your script you should add contentType
function SaveProduct(e) {
e.preventDefault(); // prevent standard form submission
$.ajax({
url: "#Url.Action("SaveProduct", "ProductManagement", new { Area = "Admin" })",
method: "post",
data: new FormData($('#formSaveProduct')[0]),
contentType: false,
processData: false,
cache: false,
contentType: "multipart/form-data", //here
success: function (result) {
if (result.success) {
$("#exampleModal").modal('toggle');
location.reload();
}
else {
$(".modal-body").html(result);
}
}
});
}
This question was answered wonderfully in my conceptual question in the following post:
IFormFile as a Nested ViewModel Property
I am working with Ajax, mvc and c#, uploading my model to my api action, using this information I found here: stackoverflow: How to append whole set of model to formdata and obtain it in MVC.
This is my problem When following step by step the above link, my model in the api side comes null:
//this is the conten of the viewmodel object in the api action
guid:{00000000-0000-0000-0000-000000000000}
descripcion:null
Imagen:null
Title:null
This is my viewmodel
public class myViewModel
{
public Guid CursoId { get; set; }
[MaxLength(125)]
public string Titulo { get; set; }
public string Descripcion { get; set; }
[Required(ErrorMessage = "select image file for course")]
[DataType(DataType.Upload)]
public HttpPostedFileBase Imagen { get; set; } //note this is the image
}
My web api, it is a simple action to test
// POST: api/Test
[Route("test")]
public void Post([FromBody]myViewModel model)
{//do something
}
this is my view:
#model ajaxtest.ViewModel.myViewModel
<form id="Editcurso" method="post" action="#" enctype="multipart/form-data">
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "Please fix the following errors.")
<div class="container">
<div class="form-group">
#Html.LabelFor(c => c.Titulo)
#Html.TextBoxFor(c => c.Titulo, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Titulo)
</div>
<div class="form-group">
#Html.LabelFor(c => c.Descripcion)
#Html.TextAreaFor(c => c.Descripcion, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Descripcion)
</div>
<div class="form-group">
#Html.LabelFor(m => m.Imagen)
#Html.TextBoxFor(m => m.Imagen, new { type = "file" })
#Html.ValidationMessageFor(m => m.Imagen)
</div>
<button id="submiter" type="submit" class="btn btn-primary">Listo!</button>
</div>
Here is my javascript:
$('#Editcurso').submit(function(e) {
e.preventDefault(); // prevent the default submit
if (!$(this).valid()) {
return; // exit the function and display the errors
}
jQuery.support.cors = true;
// Create a formdata to pass the model, since the view was generated from viewmodel, it should contain the model.
var model = new FormData($('#Editcurso').get(0));
$.ajax({
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
url: '/api/test/test',
type: 'post',
dataType: 'json',
data: JSON.stringify(model),
processData: false,
contentType: false,
success: function (data) {
console.log(data);
return false;
},
error: function () {
alert('error al postear');
return false;
}
});
e.preventDefault(); // avoid to execute the actual submit of the form.
});
});
What do you think is wrong and how can I fix it? thanks.
I use checkboxes in an MVC5 Razor View and retrieve the checkbox values from Controller successfully by using ViewBag as shown below:
Controller:
public ActionResult Create()
{
ViewBag.RolesList = new SelectList(
this.RoleManager.Roles.ToList(), "Id", "Name");
return PartialView("_Create");
}
View:
#model Identity.ApplicationGroup
#foreach (var item in (SelectList)ViewBag.RolesList)
{
<div class="checkbox">
<label>
<input type="checkbox" name="#item" value="#item.Text">
#item.Text
</label>
</div>
}
However, I cannot pass the checkbox values to the Controller via AJAX as shown below while all of the other parameters are posted without any problem. So, how can I post them as the model values?
View:
function insert(event) {
var formdata = $('#frmCreate').serialize();
$.ajax({
type: "POST",
url: '#Url.Action("Insert", "GroupsAdmin")',
cache: false,
dataType: "json",
data: formdata
});
};
Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Insert([Bind(Exclude = null)] ApplicationGroup
applicationgroup, params string[] selectedRoles)
{
...
}