Why is IFormFile collection empty when sent from Dropzone.js? - asp.net-core

I am trying to use Dropzone.js to send a collection of IFormFile (images) to the following ASP.NET Core 2.1 Api controller action:
[HttpPost("[action]")]
public async Task<IActionResult> Upload([FromForm] ICollection<IFormFile> files)
{ ... }
I am able to successfully send files to this Api from Postman. But I cannot get it to send the files from my UI, which implements Dropzone. I am using an ASP form in a Razor page
<div>
<form action="/api/images/upload"
class="dropzone needsclick dz-clickable"
id="image-upload"
method="post"
enctype="multipart/form-data">
<div class="dz-message needsclick">
<span class="note needsclick">
Drop files here or click to upload.
</span>
</div>
</form>
</div>
with the following implementation of Dropzone
/* Dropzone */
// "imageUpload" is the camelized version of the HTML element's ID
Dropzone.options.imageUpload = {
paramName: "files", // The name that will be used to transfer the file
dictDefaultMessage: "Drop files here or Click to Upload",
addRemoveLinks: true, // Allows for cancellation of file upload and remove thumbnail
init: function() {
myDropzone = this;
myDropzone.on("success", function(file, response) {
console.log("Success");
myDropzone.removeFile(file);
});
}
};
This setup - and similar variations - sends an empty collection to the Api as shown in the screenshot:
I have tried the solutions posted in similar questions on here (e.g. this, or this). I have also tried adjusting the form setup and the Dropzone configuration. Everything I have tried has not worked. As I have mentioned, above, I can post to the Api from Postman so I suspect the problem lies in my UI setup. Can anyone help?
UPDATE:
<div class="box content">
<hr>
<h2>Upload photos</h2>
<div>
<form action="/api/images/upload"
class="dropzone needsclick dz-clickable"
id="image-upload"
method="post"
enctype="multipart/form-data">
<div class="dz-message needsclick">
<span class="note needsclick">
Drop files here or click to upload.
</span>
</div>
</form>
</div>
<h2>Generated Thumbnails</h2>
<!-- <p><span id="gallery-note">Gallery refreshes from storage container image links every 5 seconds.</span></p> -->
<div id="stored-images"></div>
<!-- The Gallery as inline carousel, can be positioned anywhere on the page -->
<div id="blueimp-gallery-carousel" class="blueimp-gallery blueimp-gallery-carousel">
<div class="slides"></div>
<h3 class="title"></h3>
<a class="prev">‹</a>
<a class="next">›</a>
<a class="play-pause"></a>
<ol class="indicator"></ol>
</div>
</div>
<div class="box footer">
<hr>
<div class="privacy">
</div>
</div>
</main>
#section scripts {
<script>
// init gallery for later use
var gallery;
// Grab links for images from backend api
function fetchImageLinks() {
// Fetch images
//alert("1");
//http://localhost:61408/api/Images/thumbnails
$.get("/api/Images/thumbnails", function (fetchedImageLinks) {
//alert("2");
console.log(fetchedImageLinks)
// Check if anything is in there
if (_.isEmpty(fetchedImageLinks)) {
console.log('empty fetched')
// do nothing
} else {
// Check if we have a gallery initialized
if (_.isEmpty(gallery)) {
// initialize gallery
gallery = blueimp.Gallery(
fetchedImageLinks, // gallery links array
{
container: '#blueimp-gallery-carousel',
carousel: true
} // gallery options
);
} else {
// check if images are equal to array
console.log('currently in gallery:')
console.log(gallery.list)
var imageLinksEqual = _.isEqual(_.sortBy(gallery.list.map(s => s.split("?")[0])), _.sortBy(fetchedImageLinks.map(s => s.split("?")[0])))
if (imageLinksEqual) {
console.log('images arr are equal')
// do nothing
} else {
console.log('images arr are not equal')
// update gallery with new image urls. Only compare actual url without SAS token query string
var newImageLinks = _.difference(fetchedImageLinks.map(s => s.split("?")[0]), gallery.list.map(s => s.split("?")[0]))
console.log('differene is: ')
console.log(newImageLinks)
// Only add new images
gallery.add(newImageLinks);
// Force image load
gallery.next();
}
}
}
});
}
// Start first interval
fetchImageLinks()
setInterval(function () {
fetchImageLinks()
}, 5000)
function myParamName() {
return "files";
}
/* Dropzone */
// "imageUpload" is the camelized version of the HTML element's ID
Dropzone.options.imageUpload = {
paramName: "files", // The name that will be used to transfer the file
//uploadMultiple: true,
//paramName: myParamName,
dictDefaultMessage: "Drop files here or Click to Upload",
addRemoveLinks: true, // Allows for cancellation of file upload and remove thumbnail
init: function () {
myDropzone = this;
myDropzone.on("success", function (file, response) {
console.log("Success");
myDropzone.removeFile(file);
});
}
};
</script>
}

Check that your dropzone settings are getting applied correctly. I have tried your code as-is and it worked fine for me. However, if I removed the Dropzone configuration from the page then I get a filecount of 0.
To get around this problem put the dropzone configuration into the .cshtml page that contains the dropzone and you should see it working OK for example:
Index.cshtml
<div>
<form action="/api/images/upload"
class="dropzone needsclick dz-clickable"
id="image-upload"
method="post"
enctype="multipart/form-data">
<div class="dz-message needsclick">
<span class="note needsclick">
Drop files here or click to upload.
</span>
</div>
</form>
</div>
#section Scripts {
<script>
/* Dropzone */
// "imageUpload" is the camelized version of the HTML element's ID
Dropzone.options.imageUpload = {
paramName: "files", // The name that will be used to transfer the file
dictDefaultMessage: "Drop files here or Click to Upload",
addRemoveLinks: true, // Allows for cancellation of file upload and remove thumbnail
init: function () {
myDropzone = this;
myDropzone.on("success", function (file, response) {
console.log("Success");
myDropzone.removeFile(file);
});
}
};
</script>
}
Now, if you delete the #section from the page you will start getting a files.count of 0 when you try to upload the files.
If you want to have the dropzone configuration in a separate file then you need to ensure it is loaded into the page correctly e.g. change your scripts section to:
#section scripts {
<script src="~/scripts/dropzone-config.js"></script>
}
...using the correct path to your dropzone configuration file

Related

Any Idea how to handle file input fields when updating a form

I am using asp.net core 3.1 with Razor forms.
I have a form that contains an input of type file and it is multiple files input.
In the create form it is easy to access the file from the model.
The problem is in the update form how can handle the preview, delete adding new files to the multiple
file input.
Is there a best practice to solve such thing.
The problem is in the update form how can handle the preview, delete adding new files to the multiple file input. Is there a best practice to solve such thing.
I suggest that you could use jQuery MultiFile.
Here are the steps:
1.Download the jQuery MultiFile:https://multifile.fyneworks.com/#Install
2.Find the download zip file and extract it then move to the project wwwroot/lib folder:
For asp.net core mvc:
View:
<form asp-controller="Home" asp-action="UploadData" enctype="multipart/form-data">
<div>
<input type="file" name="files" multiple="multiple" class="multi with-preview" />
<input type="submit" value="Upload" />
</div>
</form>
#section Scripts
{
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js" type="text/javascript" language="javascript"></script>
<script src="~/lib/multifile-master/jquery.MultiFile.js"></script>
}
Controller:
[HttpPost]
public IActionResult UploadData(List<IFormFile> files)
{
//do your stuff...
return Ok();
}
For asp.net core razor pages:
Index.cshtml:
#page
#model IndexModel
<form asp-page-handler="UploadData" enctype="multipart/form-data">
<div>
<input type="file" name="files" multiple="multiple" class="multi with-preview" />
<input type="submit" value="Upload" />
</div>
</form>
#section Scripts
{
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js" type="text/javascript" language="javascript"></script>
<script src="~/lib/multifile-master/jquery.MultiFile.js"></script>
}
Index.cshtml.cs:
public class IndexModel : PageModel
{
public IActionResult OnGet()
{
return Page();
}
public IActionResult OnPostUploadData(List<IFormFile> files)
{
return Page();
}
}
Result:
I've been using bootstrap4 file input!
to load the images when updating the form I used the following way:
var filesArray = [];
$(document).ready(function ()
{
$("#photos").fileinput("refresh",
{
showUpload: false,
showRemove: false
});
loadPhotos();
setTimeout(function ()
{
if (filesArray.length > 0)
{
$(".file-drop-zone-title").remove();
$('#photos').fileinput('readFiles', filesArray);
}
}, 2500);
});
function loadPhotos()
{
//hddPhotos is a hidden input that I stored the images URLs in
var photosPath = $('#hddPhotos').val();
if (photosPath !== null && photosPath!=='')
{
var photos = jQuery.parseJSON($('#hddPhotos').val());
if (photos.length > 0)
{
var count = photos.length;
for (var i = 0; i < count; i++)
{
getBlobofImage(photos[i]);
}
}
}
}
function getBlobofImage(imagePath)
{
var blob = null;
var xhr = new XMLHttpRequest();
xhr.open("GET", imagePath);
xhr.responseType = "blob";//force the HTTP response, response-type header to be blob
xhr.onload = function ()
{
blob = xhr.response;//xhr.response is now a blob object
filesArray.push(new File([blob], /[^/]*$/.exec(imagePath)[0]));
};
xhr.send();
}

Vue File Upload with Parameters

Hi Guys i create a component to upload files and its working so far, but along with the data I'll like to pass some parameters too, for example
HTML
<div class="col-md-4">
<div class="container">
<div class="large-12 medium-12 small-12 cell">
<label>
Files
v-on:click="upload()">Submit</v-btn>
</div>
</div>
</div>
Script
import
axios.post('/api/upload', this.files)
.then(resuta);
}, error => {
console.error(error);
});
}
here (axios.post('/api/upload', this.files)) i would like to include
email: this.profile.email
Because I'm adding this parameter to the file name on my backend
Controller
[HttpPost, DisableRequestSizeLimit]
public ActionResult UploadFile(string email)
{
var files = Request.Form.Files;
foreach (var file in files)
{
}
}
}
return Ok();
}
Given this.files is a FormData instance, you should be able to set any field you want. For example
upload () {
this.files.set('email', this.profile.email)
axios.post('/api/upload', this.files)...
I don't know .NET MVC very well any more but this would add email as a form param in the request.
You can use this way;
HttpContext.Request.Form.Where(p => p.Key == "email").FirstOrDefault().Value;

.net-core Site with Partial Vue.js Frontend Form Fields

I am trying to create a site that has partial implementation of Vue.js, I am looking at using Vue.js as from what I understand it does not require a SPA site like other JS frameworks and I believe this framework ticks the boxes required.
I have a basic form that I want to be used to Create, Update and Delete objects.
The data is received via a SAL which calls an API, all Create, Update and Delete calls will go through the same API.
I have been able to do a HttpGet and HttpPost to Get and Update the data and show it on a simple form.
However when I try to display just a blank form I get the following errors:
Error Received
The code I have is as followed:
.cshtml page
#model bms.accessbookings.com.Types.ViewModels.ShowVenueViewModel
#{
ViewData["Title"] = "Venue";
}
<div class="m-grid__item m-grid__item--fluid m-wrapper">
<div class="m-content">
<div class="row">
<div id="venueForm">
Venue ID: <input type="text" v-model="venue.venueId" />
<br/>
Venue Name: <input type="text" v-model="venue.venueName" />
<br/>
Address: <input type="text" v-
model="venue.address.addressLine1"/>
<br/>
Line 2: <input type="text" v-
model="venue.address.addressLine2"/>
<br/>
City: <input type="text" v-model="venue.address.city"/>
<button type="button" v-on:click="sendToServer"
style="padding: 0; border: none; background: none;
cursor: pointer;">
<i class="la la-save"></i>
</button>
</div>
</div>
</div>
</div>
#section Scripts {
<script src="/js/venueform.js" type="text/javascript"></script>
}
.cs ViewModel:
public class ShowVenueViewModel
{
public int VenueId { get; set; }
public string VenueName { get; set; }
public Address Address { get; set; }
}
Address element contains Line1, Line2, City etc
VenueController Get and Post:
[HttpGet]
[Route("GetVenue")]
public ShowVenueViewModel GetVenue(int venueId = 0)
{
ShowVenueViewModel viewModel = new ShowVenueViewModel
{
Address = new Address()
};
if (venueId > 0)
{
viewModel = _venueImplementation.GetShowVenueViewModel(venueId);
}
return viewModel;
}
[HttpPost]
[Route("SaveVenue")]
public ShowVenueViewModel SaveVenue([FromBody]ShowVenueViewModel venueViewModel)
{
return venueViewModel;
}
.js page:
$(document).ready(function() {
var venueId = window.location.pathname.substring(7);
const vm = new Vue({
el: '#venueForm',
data () {
return {
venue: {}
}
},
props: {
currentevent: Object
},
created() {
Object.assign(this.venue, this.currentevent || {});
},
mounted: function() {
axios.get('/venue/GetVenue', { params: { venueId: venueId } }).then(response => {
this.venue = response.data;
});
},
methods: {
sendToServer: function () {
var self = this;
console.log("Venue getting updated");
axios.post('/venue/SaveVenue', self.venue)
.then(response => {
this.venue = response.data;
console.log("Venue Updated");
});
}
}
});
});
At the moment if the venue has items it returns these items without a problem and displays them into the form, I can edit the inputs and "save" them which then returns the newly saved information (save functionality not yet connected to my BLL / SAL).
However when no Venue object is returned (empty) the form does not display at all, and so there is no way to enter details onto a blank form to "save" and create a new venue.
Still really new to vue.js and I find it hard to find guides that are not pointing to CLI or SPA style sites.
I may have a lot of things wrong here, but if there are any pointers to help me I would be very grateful.
Ok well the error you're getting comes from your template (.cshtml page). You need either to make sure venue.address always has a value, or, safer, test for the presence of venue.address.addressLine1 before displaying it.
When you get an error in a render function, vue can't tell you the line number. But you know it's in the template somewhere and it's generally not hard to find. Keep your templates short :-) (the one shown is fine).

I want to show my PDF in partial view where download option are not available its mandatory?

I want to show PDF without Download Option. After many search of Google I get some answer but I'm facing a problem in this.
PDF is open in partial View, but there have also Download Option. Is there another option to open Pdf without Download option?
#model Bizzop.Models.MyAccountModel
#{
Layout = null;
}
<html>
<head>
<title>INDEX</title>
</head>
<body>
<div id="divPartialView">
</div>
<div class="container">
#if (Model.MyAccountList.Count > 0)
{
foreach (var items in Model.MyAccountList)
{
<div class="video-row">
<a href="#" target="_blank" onclick="myPdf(this)"
id="#items.PdfName">
<div class="row">
#if (items.PdfName == "" || items.PdfName == null)
{
<img src="ImageName"/>
}
else
{
<img src="ImageName"/>
}
</div>
</a>
</div>
}
}
</div>
////// This is Ajax code where we pass File name when click the user in anchor
tag
<script>
function myPdf(e) {
debugger
var filen = e.id;
$.ajax({
url: "/MyAccount/MyPdfResult",
data: { pdfname: filen },
cache: false,
type: "POST",
dataType: "html",
type: "post",
success: function (data) {
SetData(data);
},
error: function (data) {
}
});
function SetData(data) {
$("#divPartialView").html(data); // HTML DOM replace
}
}
</script>
/////////// In Controller
public ActionResult MyPdfResult(string pdfname = null)
{
string embed = "<object data=\"{0}\" type=\"application/pdf\"
width=\"500px\" height=\"300px\">";
embed += "</object>";
TempData["Embed"] = string.Format(embed,
VirtualPathUtility.ToAbsolute("~/Content/TutorialImage/TutorialPdf/"+
pdfname));
return PartialView("_Viewpdf", TempData["Embed"]);
}
///// where i am create a Partial View
<div class="ancor">
#Html.Raw(TempData["Embed"])
</div>
i just tried it on my side and it worked for me. You need to make sure that your app is allowed to access the pdf file.
This is my code:
The controller:
[HttpPost]
[AllowAnonymous]
public ActionResult MyPdfResult(string pdfname = null)
{
string embed = "<object data=\"{0}\" type=\"application/pdf\" width=\"500px\" height=\"300px\">";
embed += "If you are unable to view file, you can download from here";
embed += " or download <a target = \"_blank\" href = \"http://get.adobe.com/reader/\">Adobe PDF Reader</a> to view the file.";
embed += "</object>";
TempData["Embed"] = string.Format(embed, VirtualPathUtility.ToAbsolute("~/Files/pdf.pdf"));
return PartialView("_Viewpdf", TempData["Embed"]);
}
The partial view:
<style type="text/css">
body {
font-family: Arial;
font-size: 10pt;
}
#using (Html.BeginForm("MyPdfResult", "Home", FormMethod.Post))
{
View PDF
<hr />
#Html.Raw(TempData["Embed"])
}
The Index:
<div>
#Html.Partial("_Viewpdf");
</div>

How can I upload image in a link on the vue component?

My component vue like this :
<template>
<div>
<ul class="list-inline list-photo">
<li v-for="item in items">
<div class="thumbnail" v-if="clicked[item]">
<img src="https://myshop.co.id/img/no-image.jpg" alt="">
<span class="fa fa-check-circle"></span>
</div>
<a v-else href="javascript:;" class="thumbnail thumbnail-upload"
title="Add Image" #click="addPhoto(item)">
<span class="fa fa-plus fa-2x"></span>
</a>
</li>
</ul>
</div>
</template>
<script>
export default {
props: ['state', 'product'],
data() {
return {
items: [1, 2, 3, 4, 5],
clicked: [] // using an array because your items are numeric
}
}
},
methods: {
addPhoto(item) {
this.$set(this.clicked, item, true)
}
}
}
</script>
If I click a link then it will call method addPhoto
I want if the a link clicked, it will upload image. So it will select the image then upload it and update img with image uploaded.
It looks like the code to upload image will be put in add photo method
I'm still confused to upload image in vue component
How can I solve it?
You can use a component for file picker like this:
<template>
<input v-show="showNative" type="file" :name="name" #change="onFileChanged" :multiple="multiple" :accept="accept"/>
</template>
<script>
export default {
props: {
name: { type: String, required: true },
show: { type: Boolean, Default: false },
multiple: { type: Boolean, default: false },
accept: { type: String, default: "" },
showNative: { type: Boolean, default: false }
},
watch: {
show(value) {
if (value) {
// Resets the file to let <onChange> event to work.
this.$el.value = "";
// Opens select file system dialog.
this.$el.click();
// Resets the show property (sync technique), in order to let the user to reopen the dialog.
this.$emit('update:show', false);
}
}
},
methods: {
onFileChanged(event) {
var files = event.target.files || event.dataTransfer.files;
if (!files.length) {
return;
}
var formData = new FormData();
// Maps the provided name to files.
formData.append(this.name, this.multiple ? files : files[0]);
// Returns formData (which can be sent to the backend) and optional, the selected files (parent component may need some information about files).
this.$emit("files", formData, files);
}
}
}
</script>
And here some information how to use it:
import the component -> declare the directive.
provide a -> is used for the formData creation (is the name which is going to backend).
to display it us the property
Note: sync recommended if needed to be opened multiple times in the same page. Check the bottom examples. ( /!\ Vue 2.3 required for sync /!\ )
listen to #files event to get an array of selected files as parameter
if you want to use it as multiple file select, then provide the property as true.
use prop to filter the files (valid accept types: HTML Input="file" Accept Attribute File Type (CSV)).
when is set to true, the component displays 'select file' button (input type file), otherwise it is hidden, and windows displayed by Js.
ex:
Single select
<file-upload name="fooImport" #files="selectedFile" :show.sync="true" />
ex:
Multiple select
<file-upload name="barUpload" #files="selectedFiles" :show.sync="displayUpload" accept="text/plain, .pdf" />