Knockout: Bind uploaded files - javascript

I have ran into an issue of being unable to upload multiple files from nested models within the main knockout view model.
Testing out uploading HttpPostedFileBase properties, I was succesful in uploading a file and using two synchronous ajax calls within the EditTestStepjs file, using the XmlFile property.
However, in trying to send over multiple files that are bound to the nested models within the list, I am unable to have their values sent over to the controller using form-data.
Nested View Model
public class XmlParameterViewModel
{
public string ParameterName { get; set; }
public HttpPostedFileBase XmlValue { get; set; }
}
Main View Model
public class EditTestStepViewModel
{
public string TestStepName { get; set; }
public HttpPostedFileBase XmlFile { get; set; }
public List<XmlParameterViewModel> XmlParameters { get; set; }
public EditTestStepViewModel()
{
this.XmlParameters = new List<XmlParameterViewModel>();
}
}
Controller
[HttpPost]
public ActionResult SaveEdit(EditTestStepViewModel editTestStepViewModel)
{
return View("Index", editTestStepViewModel);
}
public JsonResult FileUpload(List<HttpPostedFileBase> xmlFile)
{
return Json(new {success = true}, JsonRequestBehavior.AllowGet);
}
EditTestStepJS file
var EditTestStepViewModel = function(data) {
var self = this;
ko.validatedObservable(ko.mapping.fromJS(data, mapping, self));
self.saveTestStep = function() {
if (self.isValid()) {
var file = new FormData($("#editForm").get(0));
$.ajax({
type: "POST",
url: "/Home/FileUpload/",
data: file,
processData: false,
contentType: false,
dataType: "json",
success: function () {
var dataToSend = ko.mapping.toJSON(self);
$.ajax({
url: "/Home/SaveEdit/",
type: "POST",
contentType: "application/json",
data: dataToSend
});
}
});
}
}
View
<form enctype="multipart/form-data" id="editForm">
<table class="table table-striped">
<tr>
<th>XmlPara</th>
</tr>
<tbody data-bind="foreach: XmlParameters">
<tr>
<td class="form-group"> <input name="ParameterName" class="form-control input-sm" data-bind="value: ParameterName" disabled="disabled"/></td>
<td class="form-group"> <input name="XmlValue" type="file" class="btn btn-group" data-bind="value: XmlValue" /></td>
</tr>
</tbody>
</table>
<button data-bind="click: saveTestStep" type="submit">Save Test Step</button>
<form>

Related

ASP.NET Core nested IFormFile list is always null

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:

Unable to bind html table data to mvc controller Model

I have this model
public class Model
{
public string itemlineId { get; set; }
public string shipmentID { get; set; }
public string containerID { get; set; }
public string containerType { get; set; }
}
I have a dynamic html table, what im trying to do is to post table data through ajax,and send it to the controller
$("body").on("click", "#btnSave", function () {
//Loop through the Table rows and build a JSON array.
var itemlists= new Array();
$("#tblAttachShip TBODY TR").each(function () {
var row = $(this);
var itemList = {};
itemList.itemlineId = row.find("TD").eq(0).html();
itemList.shipmentID = document.getElementById("txtShipmentID").value
itemList.containerID = row.find("TD").eq(1).html();
itemList.containerType = row.find("TD").eq(2).html();
itemlists.push(itemList);
});
//Send the JSON array to Controller using AJAX.
$.ajax({
type: "POST",
url: "/Project/Create",
data: JSON.stringify({ Model : Itemslists}),
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (r) {
alert(r + " record(s) inserted.");
}
});
});
so far all okay, when I try to read the post request in the browser I can see that the request has the correct data as json format
Model: [{itemlineId: "aa", shipmentID: "a", containerID: "aa", containerType: "aa"}]}
however when I check the controller the list doesn't contain any elements, and no values has been binded, I checked several posts but I can't figure ouut what I did wrong to bind the json data to the model in the controller
[HttpPost]
public JsonResult Create(List<Model> Model)
{
return Json("Success");
}
EDIT
I think we both jumped the gun a bit there. I did some digging and it looks like the JSON.stringify is actually going to be necessary because of the way that ajax posts the data; I think your issue might actually be with the javascript. When I copied your code, there were errors. I am running this right now which is working. Assuming your posted code was copy and pasted, this:
data: JSON.stringify({ Model : Itemslists}),
should be
data: JSON.stringify({ Model : itemlists}),
Looks like it was just a typo with the name of the array.
Working code:
#{
ViewBag.Title = "Home Page";
}
<script src="https://code.jquery.com/jquery-3.4.1.min.js"
integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo="
crossorigin="anonymous"></script>
<script type="text/javascript">
$("body").on("click", "#btnSave", function () {
//Loop through the Table rows and build a JSON array.
var itemlists = new Array();
$("#tblAttachShip TBODY TR").each(function () {
var row = $(this);
var itemList = {};
itemList.itemlineId = row.find("TD").eq(0).html();
itemList.shipmentID = document.getElementById("txtShipmentID").value
itemList.containerID = row.find("TD").eq(1).html();
itemList.containerType = row.find("TD").eq(2).html();
itemlists.push(itemList);
});
//Send the JSON array to Controller using AJAX.
$.ajax({
url: './Home/Create',
type: 'POST',
data: JSON.stringify({ Model: itemlists }),
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (r) {
alert(r + " record(s) inserted.");
},
error: function (r) {
alert(JSON.stringify(r));
}
});
});
</script>
<input type="text" id="txtShipmentID" />
<table id="tblAttachShip">
<tbody>
<tr>
<td>aaa</td>
<td>aa</td>
<td>a</td>
</tr>
<tr>
<td>bbb</td>
<td>bb</td>
<td>b</td>
</tr>
</tbody>
</table>
<button id="btnSave">Save</button>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace WebApplication1.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult About()
{
ViewBag.Message = "Your application description page.";
return View();
}
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
[HttpPost]
public JsonResult Create(List<Model> Model)
{
return Json(new { Message = "Success" });
}
}
public class Model
{
public string itemlineId { get; set; }
public string shipmentID { get; set; }
public string containerID { get; set; }
public string containerType { get; set; }
}
}
You are stringifying your object:
data: JSON.stringify({ Model : Itemslists}),
So you are passing a string into your controller, when your controller expects a List.
Off the top of my head i'd say try just passing the object e.g.
data: Itemslists,
Or if there is a reason that you need to pass it as a string. Change your controller to receive a string and then deserialize it:
(List<Model>)serializer.Deserialize(jsonString, typeof(List<Model>);

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

Send data to WebAPI (C#) GET Request Ajax call

I want to use html to get WebApi ReturnValue. (type:Get、datatype:Jsonp)
Now when I direct execution WebApi, it can get ReturnValue "2" (indicate execution Success).
But when via by html + Jquery + ajax , it always show error function information.
How an I resolve this problem, thanks.
------------- code -------------
API Model:
public class LoginModel
{
public int Userno { get; set; }
public List<Second> LoginSecond { get; set; }
}
public class Second
{
public string testinfo01 { get; set; }
public string testinfo02 { get; set; }
public List<Third> Third { get; set; }
}
public class Third
{
public string finValue { get; set; }
}
API Controller:
[HttpGet]
public string Get(string id, string id1) //id=Account,id1=password
{
string conn = ConfigurationManager.ConnectionStrings["testconn"].ConnectionString;
string Dictionary = Login.Login(id, id1).ToString();
return Dictionary;
}
Html:
<div class="form" id="form">
<form class="login-form" method="get">
<input type="text" id="inpAcc" placeholder="Acc"/>
<input type="password" id="inpPwd" placeholder="pwd"/>
<input id="loginbtn" type="button" value="login"></input>
<input id="data" type="label" />
</form>
</div>
jquery + ajax:
<script type="text/javascript" >
$(document).ready(function() {
$(document).on('click', '#loginbtn',function(){
var username = $("input#inpAcc").val();
var password = $("input#inpPwd").val();
alert(username);
var APIurl = "localhost:10733/Api/Login/";
$.ajax({
type: "get",
dataType: 'jsonp',
username:username,
password:password,
url: APIurl +"/"+username+"/"+password,
async: false,
headers: { "cache-control": "no-cache" },
contentType: 'application/json; charset=utf-8',
data :function (data)
{
var returnvalue = data[0].request();
console.log(data.stat);
console.log(data.status);
console.log(data.message);
console.log(data.html);
alert(returnvalue);
},
error: function(request, status, error) {
console.log(request.stat);
console.log(request.status);
console.log(request.message);
console.log(request.html);
alert(request.responseText);
},
success: function(data) {
console.log(data.stat);
console.log(data.status);
console.log(data.message);
console.log(data.html);
alert(data);
}
});
});
});
</script>
Please check your source. Remove the route configuration as below source included route config inline.
[RoutePrefix("api/Login")]
public class LoginController : ApiController
{
[HttpGet]
[Route("Get/{id}/{id1?}")]
public string Get(string id, string id1 = null) //id=Account,id1=password
{
return "Working";
}
}
to call above api your url should be like
http://localhost:11299/api/login/Get/id/id1/
In the above id1 is optional

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