upload entire model to API via AJAX (mvc c#) - javascript

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.

Related

IFormFile property in .NET Core ViewModel causing stalled AJAX Request

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

ASP.NET: POST List<Object> / Model with Ajax (JSON) from view to controller

Working in ASP.NET Core where I'm trying to POST a List of Items through Ajax. Ideally, I would like to pass the entire ReportViewModel but I haven't been able to match the signature correctly (since passing a DateTime or a List isn't that easy).
My question
How to POST a List<Object> from the view with Ajax to the controller?
OR How to POST a Model from the view with Ajax to the controller?
I currently have the following code:
Models
public class ReportViewModel {
public int ReportType { get; set; };
public DateTime DateFrom { get; set; };
public DateTime DateTo { get; set; };
public List<Item> ItemList{ get; set; };
}
public class Item {
// Properties are simple types
}
View (ReportView)
#model Namespace.ViewModels.ReportViewModel
#inject Namespace.Resource Resources
<!-- HTML code -->
<form>
<button class="ui button" type="submit" onclick="return createPDF();">#Resources.Save</button>
</form>
<script>
function createPDF() {
alertify.set('notifier', 'position', 'bottom-left');
$.ajax({
type: "POST",
url: "CreatePDF",
data: JSON.stringify({
reportType: #Model.ReportType,
ticksDateFrom: #Model.DateFrom.Ticks,
ticksDateTo: #Model.DateTo.Ticks
#* TODO: Add the List<Items> itemList (from #Model.ItemList)*#
}),
contentType: 'application/json',
// Code for success and error
});
return false;
};
</script>
Controller (ReportController)
[HttpPost]
public JsonResult CreatePDF([FromBody] dynamic data) {
// Lots of code
return Json(new { isError = false });
}
/* When passing the entire model
* [HttpPost]
* public JsonResult CreatePDF([FromBody] ReportViewModel model) {
* // Lots of code
* return Json(new { isError = false });
* }
*/
I tried passing the model as seen below but that leaves me with the parameter in the controller being null or as a new "empty" object, depending if I use [FromBody] or not.
$.ajax({
type: "POST",
url: "CreatePDF",
data: #Html.Raw(Json.Serialize(Model)),
contentType: 'application/json',
// Code for success and error
});
You can use the BeginForm in your view that posts to a controller:
#model Namespace.ViewModels.ReportViewModel
#inject Namespace.Resource Resources
#using (Html.BeginForm("CreatePDF", "[PDFControllerName]", FormMethod.Post, new { #id = "pdfCreatorForm" }))
{
<!-- Send parameters to a controller like this (invisibly) -->
#Html.HiddenFor(m => m.ReportType)
#Html.HiddenFor(m => m.DateFrom.Ticks)
#Html.HiddenFor(m => m.DateTo.Ticks)
#Html.HiddenFor(m => m.ItemList) <!-- Send the List<Items> -->
<button type="submit" class="ui button" onclick="alertify.set('notifier', 'position', 'bottom-left')">#Resources.Save</button>
}
And then you don't need the JavaScript any more, another thing to keep in mind is to keep as much functionality on your server side as you can.
If you POST the form to a controller, you can access the view's parameters etc like this:
public JsonResult CreatePDF(ReportViewModel formData)
{
int reportType = formData.ReportType;
DateTime ticksDateFrom = formData.DateFrom.Ticks;
DateTime ticksDateTo = formData.DateTo.Ticks;
List<Items> itemList = formData.ItemList;
// Lots of code
}
And, you actually doesn't need to specify that it's taking in a HttpPost :)
I don't know what exactly I changed to make it work but the following compiles correctly. Could be the processData: true and cache: false properties?
Controller
[HttpPost]
public JsonResult CreatePDF([FromBody] ReportViewModel model)
{
// Lots of code
return Json(new { isError = false });
}
View (JS only)
$.ajax({
type: "POST",
url: "CreatePDF",
data: JSON.stringify(#Html.Raw(Json.Serialize(Model))),
contentType: 'application/json; charset=utf-8',
processData: true,
cache: false,
// Code for success and error
});

Passing user input from View to Javascript function

I have a view which asks for user input. View is as below -
#(Html.Input(m => m.SchoolName).Id("SchoolName"))
#(Html.Input(m => m.Address).Id("Address"))
#(Html.Input(m => m.Phone).Id("Phone"))
<button class="btn btn-primary" name="btnSchoolSave" id="btnSave">
Submit
</button>
Then I have a javascript function, which handles the click event of the button -
function () {
$("button[name='btnSchoolSave']").on('click', function () {
$.ajax({
url: '/School/SaveSchool', //School is my controller and 'SaveSchool' is the method in the controller.
contentType: 'application/html; charset=utf-8',
type: 'POST',
dataType: 'html'
})
.success(function (result) {
alert("saved")
})
.error(function (xhr, status) {
alert(status);
})
})
};
My Controller method is like below. I have not implemented the method yet.
public void SaveSchool(Models.School model)
{
//TODO
}
My idea is - I want to get all the values inputted by the user in the View, get all those Model values, and pass it to Javascript function and javascript function in return passes the Model to the controller method and save it.
Now, I know that I can directly call my Controller action method from the view and get the saving of data taken care of. But, my requirement is to pass the data to javascript and from javascript call the method and save user input.
How can I do that?
Thanks
#model XXX.YourViewModel
<form id="your-form" style="margin: 0px;">
#Html.AntiForgeryToken()
#Html.HiddenFor(m => m.Id)
#Html.LabelFor(m => m.SchoolName)
#Html.TextBoxFor(m => m.SchoolName)
#Html.ValidationMessageFor(m => m.SchoolName)
#Html.LabelFor(m => m.Address)
#Html.TextBoxFor(m => m.Address)
#Html.ValidationMessageFor(m => m.Address)
#Html.LabelFor(m => m.Phone)
#Html.TextBoxFor(m => m.Phone)
#Html.ValidationMessageFor(m => m.Phone)
<button id="btnSchoolSave" name="edit" type="button">Save</button>
</form>
$("#btnSchoolSave").on('click', function () {
//get the form
var form = $("#your-form");
//validate form
if (!form.valid()) {
return;
}
//serialize the form
serializedForm = form.serialize();
//ajax post
$.ajax({
url: "#Url.Action("CompanyEdit", "CV")",
type: "POST",
data: serializedForm
.........
......
Now the serializedForm will be posted to your controller parameter as the ViewModel
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult SaveSchool(YourViewModel modal)
{
//find school
var school = repository.FindSchool(modal.Id)
//map value from modal
school.SchoolName = modal.SchoolName;
..........
repository.SaveScool(school);
}

Unable to send Form Data & File Upload via AJAX to controller

I am trying to send a bunch of form data from my view and map it to a ViewModel parameter in my controller. In addition, I am trying to send a file with this request which will map to a separate parameter.
When formData is sent through to the controller, it correctly maps the file upload to the file parameter, however, the model parameter properties are all null/defaults.
In summary, my question is this: how do I map my form element values to to MyViewModel paramter in my controller whilst sending a file too?
Model:
public class MyViewModel
{
public int AsssumptionSetId { get; set; }
public int BuildingBlockId { get; set; }
public string ReplacementCode { get; set; }
public decimal Rounding { get; set; }
public string DataSource { get; set; }
public bool AER { get; set; }
public int Term { get; set; }
}
View:
This view is strongly typed to the MyViewModel:
<form id="buildingBlockForm">
#Html.HiddenFor(model => model.AsssumptionSetId)
#Html.HiddenFor(model => model.BuildingBlockId)
#Html.TextBoxFor(m => m.ReplacementCode)
#Html.TextBoxFor(m => m.Rounding)
#Html.DropDownListFor(m => m.DataSource, (SelectList)ViewBag.DataSources)
#Html.DropDownListFor(m => m.Term, (SelectList)ViewBag.Terms)
#Html.CheckBoxFor(m => m.AER)
<input type="file" id="file" name="file" />
<input class="button green-button" type="submit" value="Create" />
</form>
Controller:
public ActionResult CreateBuildingBlock(MyViewModel model, HttpPostedFileBase file)
{
// all of the 'model' properties = null instead of the form values
// file = the file I chose to upload and works as expected
}
JS:
var formData = new FormData($('#buildingBlockForm'));
// Get file and append to form data (Should only be 1)
$.each(Files["csv"], function (key, value) {
formData .append("file", value);
});
// Send file
$.ajax({
url: '/Assumptions/CreateBuildingBlock',
type: 'POST',
data: formData,
cache: false,
dataType: "json",
contentType: false,
processData: false,
success: function (response) {
// Handle success
},
error: function (xhr, status, errorThrown) {
// Handle errors
}
});
Turns out I was missing an index when grabbing the form that needs serializing.
new FormData($('#buildingBlockForm')[0]);
This solved my issue.
since your form contains a file-input type, you need your form to handle this submission (enctype).
<form id="buildingBlockForm" enctype="multipart/form-data">
Also, if you want to stick with MVC's form helper, it would alleviate the issues you may have with a script-based ajax post.
#using (Ajax.BeginForm("CreateBuildingBlock", "Assumptions", null, new AjaxOptions { HttpMethod = "POST", OnSuccess = "postSuccess", OnFailure = "postFailed" }, new { enctype = "multipart/form-data" }))
{
// your form fields here
}
<script>
function postSuccess() {
// handle success here
}
function postfailed() {
// handle failed post here
}
</script>

HttpPostedfileBase is null using jQuery Ajax

I have problem with uploading file In Asp.net Mvc. First of all I should use Ajax to pass the upload file value.
In javascript I have model that I fill it, When I check it with debugger is correctly fill the object, but when I send this model to server (Controller )
The httpPostedfileBase value is Always null.
I search it on google, in some post I saw that I cant use file uploader with Ajax, but in other I saw that I can.
But I can not fix my Code.
There is my Javascript Code.
$(document).ready(function () {
$('#btnUploadFile').on('click', function () {
var data= new FormData();
debugger;
var files = $("#fileUpload").get(0).files;
if (files.length > 0) {
data.append("UploadedImage", files[0]);
}
var ResturantSharingViewModel =
{
Type: $("#SharingTargetType").val(),
SharingTitle: $("#SharingTitle").val(),
content: $("#Content").val(),
ItemId : $("#ItemId").val(),
Photos: files[0]
};
$.ajax({
type: 'POST',
dataType: 'json',
contentType: 'application/json',
url: '<%= Url.Action("SaveOneDatabase")%>',
data: JSON.stringify(ResturantSharingViewModel),
success: function (result) {
var rs = result;
},
error: function () {
alert("Error loading data! Please try again.");
}
});
My Controller public virtual bool SaveOneDatabase(ResturantSharingViewModel result)
My ResturantSharingViewModel View Model
public class ResturantSharingViewModel
{
public Guid SharingPremiumHistoryID { get; set; }
public string SharingTitle { get; set; }
public string Content { get; set; }
public DateTime AddedDate { get; set; }
public bool IsSubmit { get; set; }
public DateTime SubmitedDate { get; set; }
public IEnumerable<SelectListItem> SharingTypes { get; set; }
public IEnumerable<SelectListItem> SharingTargetType { get; set; }
public short Type { get; set; }
public Guid ItemId { get; set; }
public HttpPostedFileBase[] Photos { get; set; }
}
My Html Elements
<form enctype="multipart/form-data">
<article>
<%--<% =Html.BeginForm("Add","PremiumSharing") %>--%>
<hgroup class="radiogroup">
<h1>ارسال خبر</h1>
<%= Html.HiddenFor(model => model.SharingPremiumHistoryID) %>
<%= Html.HiddenFor(model => model.ItemId) %>
<div class="group">
<span> ارسال به </span>
<%= Html.DropDownListFor(model => model.SharingTargetType, Model.SharingTypes) %>
</div>
</hgroup>
<div class="newseditor">
<div class="input-form">
<%= Html.LabelFor(model => model.SharingTitle, "عنوان خبر") %>
<%= Html.TextBoxFor(model => model.SharingTitle) %>
</div>
<div class="input-form">
<%= Html.LabelFor(model => model.Content, "متن خبر") %>
<%= Html.TextAreaFor(model => model.Content) %>
</div>
<div><input id="fileUpload" type="file" />
</div>
<% if (ViewBag.IsInEditMode != null && !(bool)ViewBag.IsInEditMode)
{%>
<div class="input-form">
<%= Html.CheckBox("SendToInTheCity") %> ارسال در بخش «در شهر» فیدیلیو
</div>
<%} %>
<div class="input-submit">
<button name="post" id="btnUploadFile" onclick="uploadFile()" >ارسال خبر</button>
</div>
<br />
</div>
First, it's possible to upload with Ajax, the important thing is you need to set <form enctype="multipart/form-data"></form> on you form to tell it your form has an file upload input. Then you need to accept HttpPostedFileBase as an input parameter in your controller action.
Try this. Example of jquery upload code. (Taken mostly from How can I upload files asynchronously?)
function uploadFile(uploadId) {
var formData = new FormData($('form')[0]);
$.ajax({
url: '<%= Url.Action("SaveOneDatabase")%>',
type: 'Post',
beforeSend: function(){},
success: function(result){
},
xhr: function() { // Custom XMLHttpRequest
var myXhr = $.ajaxSettings.xhr();
if(myXhr.upload) { // Check if upload property exists
// Progress code if you want
}
return myXhr;
},
error: function(){},
data: formData,
cache: false,
contentType: false,
processData: false
});
}
HTML Form needs this attribute. See this post why you need it -> What does enctype='multipart/form-data' mean?
enctype="multipart/form-data"
C#
[HttpPost]
public ActionResult SaveOneDatabase(HttpPostedFileBase file)
{
}
I have modified #a moradi's answer.
JS:
//FormData:
//Consider it a normal form but with "multipart/form-data" encoding type.
//Inside it works same as XMLHttpRequest.send() method.
var model = new FormData();
model.append("File", $('#file')[0].files[0]);
model.append("Name", "Name");
$.ajax({
url: someUrl,
type: "POST",
data: model,
//contentType:
//Sets the ContentType in header.
//The default contentType is "application/x-www-form-urlencoded; charset=UTF-8". But this will prevent us sending AntiForgeryToken to service/controller.
//To prevent this contentType is set to false.
contentType: false,
//processData:
//To prevent data getting converted to string format, 'processData' option is set to false.
processData: false,
success = function (m) {...}
error = function (m) {...}
});
View Model:
public class PhotoAlbumViewModel {
public string Name { get; set; }
public HttpPostedFileBase File { get; set; }
}
Controller:
public JsonResult AddPhoto(PhotoAlbumViewModel model) {
...
}
Refrence:
Reffer following links for details: FormData , JQuery , ContentType
View:
<script/>
var add_photo_url = "#Url.Action("AddPhoto", "Gallery")";
var model = new FormData();
var i=0;//selected file index
model.append("File", files[i]);
model.append("Name", "test");
$.ajax({// and other parameter is set here
url: add_photo_url,
type: "POST",
data: model,
dataType: "json",
cache: false,
contentType: false,
processData: false
})
.always(function (result) {
});
</script>
View Model:
public class PhotoAlbumViewModel {
public string Name { get; set; }
public HttpPostedFileBase File { get; set; }
}
Controller:
public JsonResult AddPhoto(PhotoAlbumViewModel model) {
// var result =...
// and set your result;
return Json(result, JsonRequestBehavior.AllowGet);
}

Categories

Resources