Download multiple files (50mb) blazor server-side - .net-core

i can't really find a way to download a 100mb zip file from the server to the client and also show the progress while downloading. So how will this look for a normal api controller i can add to my server-side project? if lets say i have 3 files i want to download at 50mb each.
i have tried using JSInterop like this, but this is not showing the progress of the file download, and how will i do if i want to download 3 seperate files at the same time?
try
{
//converting file into bytes array
var dataBytes = System.IO.File.ReadAllBytes(file);
await JSRuntime.InvokeVoidAsync(
"downloadFromByteArray",
new
{
ByteArray = dataBytes,
FileName = "download.zip",
ContentType = "application/force-download"
});
}
catch (Exception)
{
//throw;
}
JS:
function downloadFromByteArray(options: {
byteArray: string,
fileName: string,
contentType: string
}): void {
// Convert base64 string to numbers array.
const numArray = atob(options.byteArray).split('').map(c => c.charCodeAt(0));
// Convert numbers array to Uint8Array object.
const uint8Array = new Uint8Array(numArray);
// Wrap it by Blob object.
const blob = new Blob([uint8Array], { type: options.contentType });
// Create "object URL" that is linked to the Blob object.
const url = URL.createObjectURL(blob);
// Invoke download helper function that implemented in
// the earlier section of this article.
downloadFromUrl({ url: url, fileName: options.fileName });
// At last, release unused resources.
URL.revokeObjectURL(url);
}
UPDATE:
if im using this code, it will show me the progress of the file. But how can i trigger it from my code? This way does not do it. But typing the url does.
await Http.GetAsync($"Download/Model/{JobId}");
Controller
[HttpGet("download/model/{JobId}")]
public IActionResult DownloadFile([FromRoute] string JobId)
{
if (JobId == null)
{
return BadRequest();
}
var FolderPath = $"xxxx";
var FileName = $"Model_{JobId}.zip";
var filePath = Path.Combine(environment.WebRootPath, FolderPath, FileName);
byte[] fileBytes = System.IO.File.ReadAllBytes(filePath);
return File(fileBytes, "application/force-download", FileName);
}
UPDATE 2!
i have got it download with progress and click with using JSInterop.
public async void DownloadFiles()
{
//download all selectedFiles
foreach (var file in selectedFiles)
{
//download these files
await JSRuntime.InvokeAsync<object>("open", $"Download/Model/{JobId}/{file.Name}", "_blank");
}
}
Now the only problem left is.. it only downloads the first file out of 3.

Related

Q: Can't Convert IFormFile to Binary to upload an image to imgur with asp.net

I have two questions:
1.When i use postman to upload an image, for example when i browse 15.jpg, it generates something like nDt3Vxjca/15.jpg in value column link
what is nDt3Vxjca.
public async Task<IActionResult> Upload([FromForm] IFormFileViewModel request)
{
var requestContent = new MultipartFormDataContent();
if (request.image!= null)
{
byte[] data;
using (var br = new BinaryReader(request.image.OpenReadStream()))
{
data = br.ReadBytes((int)request.image.OpenReadStream().Length);
}
ByteArrayContent bytes = new ByteArrayContent(data);
requestContent.Add(bytes, "image", request.image.FileName);
};
var client = _httpClientFactory.CreateClient();
string clientID = "abcdefg";
client.DefaultRequestHeaders.Add("Authorization", "Client-ID " + clientID);
var response = await client.PostAsync("https://api.imgur.com/3/upload", requestContent);
return Ok();
}
I copy a some lines of code to convert IformFile to binary. It still manages to upload the image but it doesn't point to my account, it returns something like this:
{"status":200,"success":true,"data":{"id":"I5oGuBd","deletehash":"qVwP3BONUUU9dr7","account_id":null,"account_url":null,"ad_type":null
account_id and account_url is null, Did i make mistake somewhere?

Is there a way to reduce the file size saved from a byte array in xamarin?

Right now im trying to save an image using DI in Xamarin Forms, but the problem im facing now is that the file im saving is way bigger than the original file that im reading from (im using bitoobitImageEditor to load the file from my device). The original image is 200kb yet the image saved is 773kb. Is there something i can do about this?
this is my file saving function
public void SavePictureToDisk(string filename, byte[] imageData)
{
var dir = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDcim);
var pictures = dir.AbsolutePath;
//adding a time stamp time file name to allow saving more than one image... otherwise it overwrites the previous saved image of the same name
string name = filename + System.DateTime.Now.ToString("yyyyMMddHHmmssfff") + ".jpeg";
string filePath = System.IO.Path.Combine(pictures, name);
try
{
System.IO.File.WriteAllBytes(filePath, imageData);
//mediascan adds the saved image into the gallery
var mediaScanIntent = new Intent(Intent.ActionMediaScannerScanFile);
mediaScanIntent.SetData(Android.Net.Uri.FromFile(new File(filePath)));
Xamarin.Forms.Forms.Context.SendBroadcast(mediaScanIntent);
}
catch (System.Exception e)
{
System.Console.WriteLine(e.ToString());
}
}
and this is my photo reading function
public ICommand GalleryButton_Clicked => new Command(async () =>
{
frontimage = await ImageEditor.Instance.GetEditedImage(null, Config);
if (frontimage != null)
{
PhotoImageFront.Source = ImageSource.FromStream(() => new MemoryStream(frontimage));
}
});

Switch from Visual Studio 2012 --> 2019 Broke async Task and FileResult

An ASP.NET MVC solution that was working fine in VS 2012 stopped working in 2019 and I cannot find what has changed to break it.
Originally I had the code in the first block - the async task would go to the file storage and retrieve the file, and then the file was sent to the browser using a FileResult that the controller called automatically. After a VERY painful change to VS 2019 and updating everything (.NET runtime, 3rd party libraries, etc.) I have the application working again except for this issue.
I tried creating a new FileStreamResult (which is in the 2nd block) but that does not get called either. When I click on a link that calls this:
<a href="/Cert/File?folder=&filename=#HttpUtility.UrlEncode(FilePath)" ...
It gives me a blank page instead of downloading the file as it used to.
public async Task FileAsync(string folder, string filename)
{
AsyncManager.OutstandingOperations.Increment();
var ReadObjectTask = _fileStorageProvider.ReadObjectDataAsync(folder, filename);
Stream ROResult = await ReadObjectTask;
AsyncManager.Parameters["stream"] = ROResult;
AsyncManager.Parameters["filename"] = filename;
AsyncManager.OutstandingOperations.Decrement();
}
public FileResult FileCompleted(Stream stream, string filename)
{
if (stream == null)
{
return File(Server.MapPath(Url.Content("~/Content/bad_file.png")), "image/png");
}
var file = new FileStreamResult(stream, MIMEAssistant.GetMIMEType(filename));
if (filename.Contains("/"))
{
filename = filename.Split('/').Last();
}
file.FileDownloadName = filename;
return file;
}
Here is the FileStreamResult I tried:
public System.Web.Mvc.FileStreamResult FileCompleted(Stream stream, string contentType, string filename)
{
if (stream == null)
{
string bFile = Server.MapPath(Url.Content("~/Content/bad_file.png"));
Stream blankfile = System.IO.File.OpenRead(bFile);
return File(blankfile, MIMEAssistant.GetMIMEType(bFile), System.IO.Path.GetFileName(bFile));
}
if (filename.Contains("/"))
{
filename = filename.Split('/').Last();
}
return File(stream, MIMEAssistant.GetMIMEType(filename), filename);
}
(The filename.Contains part is old code from a predecessor that I just need to replace with Path.GetFileName - sorry I did not clean it up before I posted.)
I decided to make the Async Task one of type and moved the
stream processing into that procedure to solve my problem. I do not know
why the Async Task that was working in 2012 stopped in 2019.
public async Task<FileResult> FileAsync(string folder, string filename)
{
AsyncManager.OutstandingOperations.Increment();
var ReadObjectTask = _fileStorageProvider.ReadObjectDataAsync(folder, filename);
Stream ROResult = await ReadObjectTask;
AsyncManager.Parameters["stream"] = ROResult;
AsyncManager.Parameters["filename"] = filename;
AsyncManager.OutstandingOperations.Decrement();
if (ROResult == null)
{
return File(Server.MapPath(Url.Content("~/Content/bad_file.png")), "image/png");
}
var file = new FileStreamResult(ROResult, MIMEAssistant.GetMIMEType(filename));
file.FileDownloadName = System.IO.Path.GetFileName(filename);
return file;
}

How to get file size in MultipartStreamProvider GetStream when missing from ContentDisposition

I creating a custom MultipartStreamProvider to store files in Azure File Storage as part of a Lift & Shift effort of a legacy application. The client is using AngularJS on the front end and WebAPI on the backend. When I am trying to use the MultipartStreamProvider, I need to implement GetStream to return a stream for it to write to. I am using cloudFile.OpenWrite which asks for the size of the stream/file that will be written to it. However, in GetStream, the ContentDisposition.Size is empty. Is there a way I can either make the AngularJS send the content size for each file or on the backend, maybe I can dig the size of the file stream from somewhere else? Any help would be greatly appreciated. Thanks!
MultipartStreamProvider
public override Stream GetStream(HttpContent parent, HttpContentHeaders headers)
{
// For form data, Content-Disposition header is a requirement
ContentDispositionHeaderValue contentDisposition = headers.ContentDisposition;
Console.WriteLine(files.Count);
if (contentDisposition != null)
{
// create default filename if its missing
contentDisposition.FileName = (String.IsNullOrEmpty(contentDisposition.FileName) ? $"{Guid.NewGuid()}.data" : contentDisposition.FileName);
// We won't post process files as form data
_isFormData.Add(false);
CloudMultipartFileData fileData = new CloudMultipartFileData(headers, _fileRepository.BaseUrl, contentDisposition.FileName);// your - aws - filelocation - url - maybe);
_fileData.Add(fileData);
var azureStream = _fileRepository.GetWriteStream(contentDisposition.Size, _relativeDirectory, fileData.FileName);
return azureStream;
// We will post process this as form data
_isFormData.Add(true);
}
throw new InvalidOperationException("Did not find required 'Content-Disposition' header field in MIME multipart body part..");
}
Actual Call to Azure
public override Stream GetWriteStream(long? fileSize, string relativeDirectory, string filename)
{
var combinedRelativeDirectory = GetCloudDirectory(relativeDirectory);
CloudFile cloudFile = combinedRelativeDirectory.GetFileReference(filename);
return cloudFile.OpenWrite(fileSize, null, null);
}
AngularJS File Upload Code
/********************************** Add/Upload Photos **************************************/
$scope.$watch('files', function (files) {
$scope.formUpload = false;
console.log(files);
if (files != null) {
for (var i = 0; i < files.length; i++) {
$scope.errorMsg = null;
(function (file) {
upload(file);
})(files[i]);
}
}
});
function upload(file) {
file.upload = Upload.upload({
url: window.location.origin + "/api/mydocs/uploadfile?storeFolder=" + $scope.attachmentFolder + "&storeId=" + $scope.storeId + "&userId=" + $scope.currentUser.UserId,
method: 'POST',
headers: {},
fields: {},
file: file
});
I wound up manually adding the file size to the header
function upload(file) {
file.upload = Upload.upload({
url: window.location.origin + "/api/mydocs/uploadfile?storeFolder=" + $scope.myFolder + "&clientId=" + $scope.clientId,
method: 'POST',
headers: { 'file-info':file.name + "-/" + file.size },
fields: {},
file: file
});
And then in the constructor, I use that to create a lookup table:
public MyCloudMultipartFormDataStreamProvider(string relativeDirectory, IEnumerable<string> lookupInfo)
{
NewFileNames = new Dictionary<string, string>();
_fileRepository = new CloudFileRepository();
_relativeDirectory = relativeDirectory;
_uploadedFilesLookup = new Dictionary<string, long>();
foreach (var fileInfo in lookupInfo)
{
var values = Regex.Split(fileInfo, #"-/");
_uploadedFilesLookup.Add(values[0], Int64.Parse(values[1]));
}
}
Then grab the file's size out of the lookup table and pass that to my GetWriteStream method
var azureStream = _fileFacade.GetWriteStream(_uploadedFilesLookup[fileName],
_relativeDirectory, fileData.FileName, out newFileName);

Generating PDFs using Phantom JS on .NET applications

I have been looking into phantomJS and looks like it could be a great tool to use generating PDFs. I wonder if anyone have successfully used it for their .NET applications.
My specific question is: how would you use modules like rasterize.js on the server, receive requests and send back generated pdfs as a response.
My general question is: is there any best practice for using phantomJS with .NET Applications. What would be the best way to achieve it?
I am fairly new in .NET World and I would appreciate the more detailed answers. Thanks everyone. :)
I don't know about best practices, but, I'm using phantomJS with no problems with the following code.
public ActionResult DownloadStatement(int id)
{
string serverPath = HttpContext.Server.MapPath("~/Phantomjs/");
string filename = DateTime.Now.ToString("ddMMyyyy_hhmmss") + ".pdf";
new Thread(new ParameterizedThreadStart(x =>
{
ExecuteCommand("cd " + serverPath + #" & phantomjs rasterize.js http://localhost:8080/filetopdf/" + id.ToString() + " " + filename + #" ""A4""");
})).Start();
var filePath = Path.Combine(HttpContext.Server.MapPath("~/Phantomjs/"), filename);
var stream = new MemoryStream();
byte[] bytes = DoWhile(filePath);
return File(bytes, "application/pdf", filename);
}
private void ExecuteCommand(string Command)
{
try
{
ProcessStartInfo ProcessInfo;
Process Process;
ProcessInfo = new ProcessStartInfo("cmd.exe", "/K " + Command);
ProcessInfo.CreateNoWindow = true;
ProcessInfo.UseShellExecute = false;
Process = Process.Start(ProcessInfo);
}
catch { }
}
public ViewResult FileToPDF(int id)
{
var viewModel = file.Get(id);
return View(viewModel);
}
private byte[] DoWhile(string filePath)
{
byte[] bytes = new byte[0];
bool fail = true;
while (fail)
{
try
{
using (FileStream file = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
bytes = new byte[file.Length];
file.Read(bytes, 0, (int)file.Length);
}
fail = false;
}
catch
{
Thread.Sleep(1000);
}
}
System.IO.File.Delete(filePath);
return bytes;
}
Here is the action flow:
The user clicks on a link to DownloadStatement Action. Inside there, a new Thread is created to call the ExecuteCommand method.
The ExecuteCommand method is responsible to call phantomJS. The string passed as an argument do the following.
Go to the location where the phantomJS app is and, after that, call rasterize.js with an URL, the filename to be created and a print format. (More about rasterize here).
In my case, what I really want to print is the content delivered by the action filetoupload. It's a simple action that returns a simple view. PhantomJS will call the URL passed as parameter and do all the magic.
While phantomJS is still creating the file, (I guess) I can not return the request made by the client. And that is why I used the DoWhile method. It will hold the request until the file is created by phantomJS and loaded by the app to the request.
If you're open to using NReco.PhantomJS, which provides a .NET wrapper for PhantomJS, you can do this very succinctly.
public async Task<ActionResult> DownloadPdf() {
var phantomJS = new PhantomJS();
try {
var temp = Path.Combine(Path.GetTempPath(),
Path.ChangeExtension(Path.GetRandomFileName(), "pdf")); //must end in .pdf
try {
await phantomJS.RunAsync(HttpContext.Server.MapPath("~/Scripts/rasterize.js"),
new[] { "https://www.google.com", temp });
return File(System.IO.File.ReadAllBytes(temp), "application/pdf");
}
finally {
System.IO.File.Delete(temp);
}
}
finally {
phantomJS.Abort();
}
}
Here's some very basic code to generate a PDF using Phantom.JS but you can find more information here: https://buttercms.com/blog/generating-pdfs-with-node
var webPage = require('webpage');
var page = webPage.create();
page.viewportSize = { width: 1920, height: 1080 };
page.open("http://www.google.com", function start(status) {
page.render('google_home.pdf, {format: 'pdf', quality: '100'});
phantom.exit();
});

Resources