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.
I'm encountering a problem sending files stored in a database back to the user in ASP.NET MVC. What I want is a view listing two links, one to view the file and let the mimetype sent to the browser determine how it should be handled, and the other to force a download.
If I choose to view a file called SomeRandomFile.bak and the browser doesn't have an associated program to open files of this type, then I have no problem with it defaulting to the download behavior. However, if I choose to view a file called SomeRandomFile.pdf or SomeRandomFile.jpg I want the file to simply open. But I also want to keep a download link off to the side so that I can force a download prompt regardless of the file type. Does this make sense?
I have tried FileStreamResult and it works for most files, its constructor doesn't accept a filename by default, so unknown files are assigned a file name based on the URL (which does not know the extension to give based on content type). If I force the file name by specifying it, I lose the ability for the browser to open the file directly and I get a download prompt. Has anyone else encountered this?
These are the examples of what I've tried so far.
//Gives me a download prompt.
return File(document.Data, document.ContentType, document.Name);
//Opens if it is a known extension type, downloads otherwise (download has bogus name and missing extension)
return new FileStreamResult(new MemoryStream(document.Data), document.ContentType);
//Gives me a download prompt (lose the ability to open by default if known type)
return new FileStreamResult(new MemoryStream(document.Data), document.ContentType) {FileDownloadName = document.Name};
Any suggestions?
UPDATE:
This questions seems to strike a chord with a lot of people, so I thought I'd post an update. The warning on the accepted answer below that was added by Oskar regarding international characters is completely valid, and I've hit it a few times due to using the ContentDisposition class. I've since updated my implementation to fix this. While the code below is from my most recent incarnation of this problem in an ASP.NET Core (Full Framework) app, it should work with minimal changes in an older MVC application as well since I'm using the System.Net.Http.Headers.ContentDispositionHeaderValue class.
using System.Net.Http.Headers;
public IActionResult Download()
{
Document document = ... //Obtain document from database context
//"attachment" means always prompt the user to download
//"inline" means let the browser try and handle it
var cd = new ContentDispositionHeaderValue("attachment")
{
FileNameStar = document.FileName
};
Response.Headers.Add(HeaderNames.ContentDisposition, cd.ToString());
return File(document.Data, document.ContentType);
}
// an entity class for the document in my database
public class Document
{
public string FileName { get; set; }
public string ContentType { get; set; }
public byte[] Data { get; set; }
//Other properties left out for brevity
}
public ActionResult Download()
{
var document = ...
var cd = new System.Net.Mime.ContentDisposition
{
// for example foo.bak
FileName = document.FileName,
// always prompt the user for downloading, set to true if you want
// the browser to try to show the file inline
Inline = false,
};
Response.AppendHeader("Content-Disposition", cd.ToString());
return File(document.Data, document.ContentType);
}
NOTE: This example code above fails to properly account for international characters in the filename. See RFC6266 for the relevant standardization. I believe recent versions of ASP.Net MVC's File() method and the ContentDispositionHeaderValue class properly accounts for this. - Oskar 2016-02-25
I had trouble with the accepted answer due to no type hinting on the "document" variable: var document = ... So I'm posting what worked for me as an alternative in case anybody else is having trouble.
public ActionResult DownloadFile()
{
string filename = "File.pdf";
string filepath = AppDomain.CurrentDomain.BaseDirectory + "/Path/To/File/" + filename;
byte[] filedata = System.IO.File.ReadAllBytes(filepath);
string contentType = MimeMapping.GetMimeMapping(filepath);
var cd = new System.Net.Mime.ContentDisposition
{
FileName = filename,
Inline = true,
};
Response.AppendHeader("Content-Disposition", cd.ToString());
return File(filedata, contentType);
}
To view file (txt for example):
return File("~/TextFileInRootDir.txt", MediaTypeNames.Text.Plain);
To download file (txt for example):
return File("~/TextFileInRootDir.txt", MediaTypeNames.Text.Plain, "TextFile.txt");
note: to download file we should pass fileDownloadName argument
Darin Dimitrov's answer is correct. Just an addition:
Response.AppendHeader("Content-Disposition", cd.ToString()); may cause the browser to fail rendering the file if your response already contains a "Content-Disposition" header. In that case, you may want to use:
Response.Headers.Add("Content-Disposition", cd.ToString());
I believe this answer is cleaner, (based on
https://stackoverflow.com/a/3007668/550975)
public ActionResult GetAttachment(long id)
{
FileAttachment attachment;
using (var db = new TheContext())
{
attachment = db.FileAttachments.FirstOrDefault(x => x.Id == id);
}
return File(attachment.FileData, "application/force-download", Path.GetFileName(attachment.FileName));
}
Below code worked for me for getting a pdf file from an API service and response it out to the browser - hope it helps;
public async Task<FileResult> PrintPdfStatements(string fileName)
{
var fileContent = await GetFileStreamAsync(fileName);
var fileContentBytes = ((MemoryStream)fileContent).ToArray();
return File(fileContentBytes, System.Net.Mime.MediaTypeNames.Application.Pdf);
}
FileVirtualPath --> Research\Global Office Review.pdf
public virtual ActionResult GetFile()
{
return File(FileVirtualPath, "application/force-download", Path.GetFileName(FileVirtualPath));
}
Action method needs to return FileResult with either a stream, byte[], or virtual path of the file. You will also need to know the content-type of the file being downloaded. Here is a sample (quick/dirty) utility method. Sample video link
How to download files using asp.net core
[Route("api/[controller]")]
public class DownloadController : Controller
{
[HttpGet]
public async Task<IActionResult> Download()
{
var path = #"C:\Vetrivel\winforms.png";
var memory = new MemoryStream();
using (var stream = new FileStream(path, FileMode.Open))
{
await stream.CopyToAsync(memory);
}
memory.Position = 0;
var ext = Path.GetExtension(path).ToLowerInvariant();
return File(memory, GetMimeTypes()[ext], Path.GetFileName(path));
}
private Dictionary<string, string> GetMimeTypes()
{
return new Dictionary<string, string>
{
{".txt", "text/plain"},
{".pdf", "application/pdf"},
{".doc", "application/vnd.ms-word"},
{".docx", "application/vnd.ms-word"},
{".png", "image/png"},
{".jpg", "image/jpeg"},
...
};
}
}
If, like me, you've come to this topic via Razor components as you're learning Blazor, then you'll find you need to think a little more outside of the box to solve this problem. It's a bit of a minefield if (also like me) Blazor is your first forray into the MVC-type world, as the documentation isn't as helpful for such 'menial' tasks.
So, at the time of writing, you cannot achieve this using vanilla Blazor/Razor without embedding an MVC controller to handle the file download part an example of which is as below:
using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers;
[Route("api/[controller]")]
[ApiController]
public class FileHandlingController : ControllerBase
{
[HttpGet]
public FileContentResult Download(int attachmentId)
{
TaskAttachment taskFile = null;
if (attachmentId > 0)
{
// taskFile = <your code to get the file>
// which assumes it's an object with relevant properties as required below
if (taskFile != null)
{
var cd = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
{
FileNameStar = taskFile.Filename
};
Response.Headers.Add(HeaderNames.ContentDisposition, cd.ToString());
}
}
return new FileContentResult(taskFile?.FileData, taskFile?.FileContentType);
}
}
Next, make sure your application startup (Startup.cs) is configured to correctly use MVC and has the following line present (add it if not):
services.AddMvc();
.. and then finally modify your component to link to the controller, for example (iterative based example using a custom class):
<tbody>
#foreach (var attachment in yourAttachments)
{
<tr>
<td>#attachment.Filename </td>
<td>#attachment.CreatedUser</td>
<td>#attachment.Created?.ToString("dd MMM yyyy")</td>
<td><ul><li class="oi oi-circle-x delete-attachment"></li></ul></td>
</tr>
}
</tbody>
Hopefully this helps anyone who struggled (like me!) to get an appropriate answer to this seemingly simple question in the realms of Blazor…!
I have recently started to use the octokit library to retrieve data from a github repository.
I can form a search request like
var request = new SearchCodeRequest("ValueSets", "xyzconnect", "projectA")
{
// we can restrict search to the file, path or search both
In = new[] { CodeInQualifier.Path },
};
var result = await client.Search.SearchCode(request);
or retrieve directly using
var xsx1 = await client.Repository.Content.GetAllContents(repository.Id, "ValueSets");
This works fine when I am using the default branch (usually master) but how do I perform the same functions against other branches?
EDIT
So to get content from branches you use the API as such
var xsx2 = await client.Repository.Content.GetAllContentsByRef(repository.Id, "ValueSets","develop");
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.
Is there a way to synchronously process an uploaded file POSTed to a controller in the ASP.Net Web API?
I've tried the process Microsoft proposed here, and it works as described, but I'd like to return something other than a Task<> from the Controller method in order to match the rest of my RESTful API.
Basically, I'm wondering if there is there any way to make this work:
public MyMugshotClass PostNewMugshot(MugshotData data){
//get the POSTed file from the mime/multipart stream <--can't figure this out
//save the file somewhere
//Update database with other data that was POSTed
//return a response
}
Again, I have made the asynchronous example work but am hoping for a way to process the uploaded file before responding to the client.
public class UploadController : ApiController
{
public async Task<HttpResponseMessage> Post()
{
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
var appData = HostingEnvironment.MapPath("~/App_Data");
var folder = Path.Combine(appData, Guid.NewGuid().ToString());
Directory.CreateDirectory(folder);
var provider = new MultipartFormDataStreamProvider(folder);
var result = await Request.Content.ReadAsMultipartAsync(provider);
if (result.FileData.Count < 1)
{
// no files were uploaded at all
// TODO: here you could return an error message to the client if you want
}
// at this stage all files that were uploaded by the user will be
// stored inside the folder we specified without us needing to do
// any additional steps
// we can now read some additional FormData
string caption = result.FormData["caption"];
// TODO: update your database with the other data that was posted
return Request.CreateResponse(HttpStatusCode.OK, "thanks for uploading");
}
}
You might notice that the uploaded files are stored inside the specified folder with names that might look like this: BodyPart_beddf4a5-04c9-4376-974e-4e32952426ab. That's a deliberate choice that the Web API team made that you could override if you want.