Consider the following code snippet
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create(MyViewModel viewModel)
{
if (ModelState.IsValid)
{
//map properties here
using (var context = new MyEntities())
{
context.Users.Add(user);
context.SaveChanges();
}
if (Request.Files.Count > 0)
{
foreach (string fileName in Request.Files)
{
HttpPostedFileBase file = Request.Files[fileName];
if (file != null && file.ContentLength > 0)
{
//do checks and upload file here
}
}
}
}
return RedirectToAction("Index", "Home");
}
The form can be submitted as a standalone or with files which then get uploaded to a server. Now my issue is If I submit the form without any files or just one file then everything works as expected. However users can upload more than one file at a time and that's where the problem comes in. The files get uploaded but I get more than one entry in the database for that particular form. For example if the user uploads three files I'll get three entries in the database exactly that same.
So my question is how do I get around this?
On the client side I'm using DropZoneJs and calling the method as
<script>
Dropzone.autoDiscover = false;
var myDropZone = new Dropzone("#dzUpload", {
url: "/Home/Create",
autoProcessQueue: false,
previewsContainer: ".preview",
});
$("#submit-all").attr("type", "button").on('click', function (e) {
e.preventDefault();
e.stopPropagation();
if (myDropZone.getQueuedFiles().length > 0) {
myDropZone.options.autoProcessQueue = true;
myDropZone.processQueue();
}
else {
$("#dzUpload").submit();
}
});
</script>
I've also come across this question but I still have the same issue
It looks like the uploadMultiple option will change the behavior so only one request is sent to the server.
var myDropZone = new Dropzone("#dzUpload", {
url: "/Home/Create",
autoProcessQueue: false,
previewsContainer: ".preview",
uploadMultiple: true,
});
So if I am right the plugin will post the form for every file you drop into your plugin right?? One way is to generate a a GUID and maintain it in your form hidden input. So every time your plugin posts it will post this GUID as well. So change your insert statement into a upsert ( update or insert) based on the Guid.. You must save this GUID also along with your other data..
So every time you intend to insert check if the GUID already exist If so update it else insert new record.
Related
I am trying to upload multiple files and then passing it to my Action in controller. The code is working fine but I am sure it can be made better because right now I am doing it sort of manually.
I am getting number of records from database against which I want to show file upload element. Number of records is not fixed, and it could be anywhere from 1 to 10 or may be more.
HTML Code
#{
foreach(FileModel fileModel in Model.FileModel)
{
<input type="file" id="#("originalFile" + i)" />
}
}
AJAX Code
for(i = 1; i <= #Model.FileModel.Count; i++)
{
if($("#originalFile" + i).val() == "")
{
alert("You must upload all files");
return false;
}
else
model.append("originalFile" + i, $("#originalFile" + i)[0].files[0]);
}
C# Code
[HttpPost]
public ActionResult UploadFiles(string masterRecordID, HttpPostedFileBase originalFile1 = null, HttpPostedFileBase originalFile2 = null, HttpPostedFileBase originalFile3 = null, HttpPostedFileBase originalFile4 = null, HttpPostedFileBase originalFile5 = null)
{
}
As you can see above is poor code as I am manually passing all files one by one. And number of files could be anything so how can make it dynamic i.e. somehow passing array or object of files from AJAX and then reading them all in controller?
Actually, #QingGuo's answer (which he previously deleted) is correct in the concept.
Your UploadFiles API action needs to modify for originalFiles parameter as array/list to support multiple files.
public ActionResult UploadFiles(string masterRecordID, HttpPostedFileBase[] originalFiles)
And in your JS part:
model.append("originalFiles", $("#originalFile" + i)[0].files[0]);
The scenario: A button allows a user to merge a large number of PDF documents to download as a single PDF. Currently the action of getting all the PDF's and merging can take upwards of a minute or more while the user has to wait for the download to start.
My goal is to allow the user to leave if they want. The solution I thought of would be to merge the documents in the background on the server and then email a link to the user when it's completed but I'm open to other solutions.
The thing that I don't understand is how to perform the merging asynchronously in the background. Using .NET, MVC 5, DevExpress.
The code looks kinda like:
$.ajax({
type: "POST",
url: '#Url.Action("ExportMergedDocuments_PersonnelId", "Personnel", new { personnelId = Model.Id })',
}).done(function(data) {
window.location.href = '#Url.RouteUrl(new { Controller = "Personnel", Action = "Download"})/?file=' + data.fileName; }
});
[HttpPost]
public JsonResult ExportMergedDocuments_PersonnelId(int PersonnelId)
{
var allDocuments = new DataSet();
allDocuments.Merge(GetDocuments((int)PersonnelId, ".....1").Tables[0]);
allDocuments.Merge(GetDocuments((int)PersonnelId, ".....2").Tables[0]);
string fileName = $"merged__{DateTime.Now.ToString("yyyyMMddHHmm")}.pdf";
if (MergePdfSet(fileName, allDocuments))
return Json(new { fileName });
// else error msg
}
Download the file:
[HttpGet]
public ActionResult Download(string file)
{
return File(..fullpath.., "application/pdf", file);
}
Merging Pdfs:
public bool MergePdfSet(string fileName, DataSet allDocuments)
{
bool merged = false;
string fullPath = Path.Combine(Server.MapPath("~/App_Data/temp/"), fileName);
using (var pdfDocumentProcessor = new PdfDocumentProcessor())
{
pdfDocumentProcessor.CreateEmptyDocument(fullPath);
foreach (DataRow row in allDocuments.Tables[0].Rows)
{
var documentId = (int)row["DocumentID"];
var fetchedDocument = GetFile(documentId);
pdfDocumentProcessor.AppendDocument(fetchedDocument);
merged = true;
}
}
return merged;
}
Two option comes to mind:
Create a new thread and run the code there but don't await it.
Use Hangfire (https://www.hangfire.io/), you can easy enqueue a job.
Have searched for solutions and also tried different things but unable to find solution for this one. So my question is:
I have a form with fields like name,email,contact no for registration. Once i click on the button what i am doing is that i am allowing user to download a file for which i have written the code as below
[HttpPost]
public ActionResult MBA_Programme(mba_programmeViewModel vm)
{
if (vm.mba_Id == null || vm.mba_Id == 0)
{
vm.Created_Date = DateTime.Now;
vm.Created_By = User.Identity.Name.ToString();
admin.addMbaDetail(vm);
if (vm.Flag != null)
{
ModelState.Clear();
return new FilePathResult(Server.MapPath("~/eiclandingpages/EIC-MBA.doc"), "application/msword");
}
else
{
return RedirectToAction("MBA_Programme", "EIC");
}
}
else
{
return RedirectToAction("MBA_Programme", "EIC");
}
}
The issue which i am facing is that once the form is getting downloaded the page is not getting refresh . I tried modelstate.clear() but not able to do it . I tried jquery also for location.reload but it refreshes but file wont downlaod and also refresh with delay.
Kindly help.
p.s: file i am getting from the folder in the project
We are working on a file upload and have chosen the blueimp plugin. We have this working in the following way.
User selects all files they wish to upload and clicks a button to upload all in one go.
This part works fine and sends all files at once to the server.
The server code sample only is as follows.
public async Task<ActionResult> Upload()
{
var result = await new Uploader().UploadFile(Request.Files[0]);
return Json(result);
}
public class Uploader
{
public async Task<UploadResult> UploadFile(HttpPostedFileBase file)
{
//do file processing and save file to disk
var filerecord = _fileService.GetById(xxxx);
filerecord.files.Add(new Info { name = file.FileName, size = file.ContentLength });
//Here for us its Mongo but any not sure what the result would be against another db
await _fileService.SaveAsync(filerecord);
return new UploadResult { message = "ok" };
}
}
//js
fi.fileupload({
url: 'uploadfile',
dataType: 'json',
autoUpload: false,
singleFileUploads:false,
maxNumberOfFiles: maxFiles,
acceptFileTypes: /(\.|\/)(jpe?g)$/i,
maxFileSize: 1048576*2, //1MB
messages: { },
// Enable image resizing, except for Android and Opera,
// which actually support image resizing, but fail to
// send Blob objects via XHR requests:
disableImageResize: true,
previewMaxWidth: 50,
previewMaxHeight: 50,
previewCrop: false,
dropZone: $('#dropzone')
});
fi.on('fileuploadadd', function (e, data) {
$('form').submit(function (e) {
data.submit();
return false;
});
});
Just a note that the db in this case is Mongo
The Problem
Uploading single files at a time i.e select a file click upload all works as expected.
Select more than one file and hit upload button all files are uploaded and saved to the disk without an issue. However I would expect the following behaviour
Get record from db
Add info about the file
Save the record
As mentioned this works great with one file at a time but when they all come in at once I am guessing the async behaviour causes the method to loose context of what file its working on as the 3 steps in the debugger seem to be called per file randomly rather than in order.
I have tried various approaches like change the order of the way things happen, also using ConfigureAwait etc.
Any advice would be appreciated.
If i understand your case correctly this code should work for you:
public async Task<ActionResult> Upload()
{
var files = new HttpPostedFileBase[Request.Files.Count];
Request.Files.CopyTo(files, 0);
var tasks = files.Select(f=>new Uploader().UploadFile(f));
await Task.WhenAll(tasks);
return Json(tasks.Where(v=>v.Status == TaskStatus.RanToCompletion ).Select(v=>v.Result).ToList());
}
My form looks like this
{
using (Ajax.BeginForm("Log",
new AjaxOptions {
UpdateTargetId = "lessonTable"
}))
//removed dropdown list ect... for readability
input type="submit" name = "submitButton" value = "Filter"
input type="submit" name = "submitButton" value = "Print Report"
and my controller does this
[HttpPost]
public ActionResult Log(lesson lesson,string submitButton)
{
/*Retreive all lessons*/
List<lesson> lessonList = (from l in storeDB.lessons
where l.statusID != DELETED
select l).ToList();
/*Filter retreived Lesson*/
lessonList = filterLesson(lesson,lessonList);
switch (submitButton)
{
case "Filter":
return PartialView(lessonList);
default:
{
return DetailsReport();
}
}
}
the DetailsReport() method returns a File
return File(renderedBytes, mimeType);
when click the Print Report button it is updating the div with the file header not requesting that the user opens the file. I have tried removing updatetarget ID but it doesn't prompt for a file download. Also when I make an ajax.actionlink call to the details report method it is working fine.
Thanks
If I understand what you are doing, it is working as intended; because you are making the request from an Ajax.BeginForm(), it is trying to display what ever is returned in the page. Removing the target would not be expected to change that.
You should simply make the "Print Report" button be part of a separate form or not a form at all, and have it pull the info it needs from the existing form before submitting (since it wouldn't do it automatically once it is removed from that form)