I'm building Blazor server app .net5 , I'm using the following code for downloading files:
//After button click
else if(buttonName == "Download")
{
JSRuntime.InvokeVoidAsync("downloadFromUrl", new { Url = "api/files", FileName = "test.pdf" });
}
//this is the function for the download proccess
function downloadFromUrl(options) {
var _a;
var anchorElement = document.createElement('a');
anchorElement.href = options.url;
anchorElement.download = (_a = options.fileName) !== null && _a !== void 0 ? _a : '';
anchorElement.click();
anchorElement.remove();
}
//# sourceMappingURL=helper.js.map
The above half-works, I do start a download but the file I get downloaded is corrupt, the size of
the file is much smaller compared to the original file, no errors I can post here, I don't
understand what could be wrong, any ideas ?
I'm not sure using InvokeVoidAsync and a fake anchor is ideal. Here is another approach.
Create a middleware. Register it in your Startup.cs, above UseStaticFiles().
In the Invoke of the middleware, retrieve the rawUrl
string rawUrl = context.Request.Path.ToString() + context.Request.QueryString.ToString();
If in this rawUrl, you recognise an URL for a file download, then process it and return. Otherwise await _next(context);.
The process (where I wrote "process it") will be:
byte[] bytes = System.IO.File.ReadAllBytes("..."); // or anything else, e.g. the bytes come from a DB
context.Response.ContentType = "application/pdf"; // should be adapted to the file type
context.Response.Headers.Add("Content-Disposition", "attachment; filename=\"myFileName.pdf\"; size=" + bytes.Length.ToString());
context.Response.Body.WriteAsync(bytes);
In the HTML source, you don't have to create a button with a click handler. Just place an anchor, with an HREF recognized by the middleware.
Middleware info : https://learn.microsoft.com/en-us/dotnet/architecture/blazor-for-web-forms-developers/middleware, see Custom middleware
Related
I am downloading files from server using javascript blob something like
let blob = new Blob([resposne['_body']], { type: contentType });
if (navigator.msSaveBlob) {
navigator.msSaveOrOpenBlob(blob, fileName); // in case of IE
} else {
let objectUrl = window.URL.createObjectURL(blob);
window.open(objectUrl);
}
The code above works fine but in IE it shows a dialog:
Also if I put a direct pdf link in the href tag then it also works fine. So it looks like there is no problem with the adobe plugin.
What I want is to directly open the file instead of showing this prompt. I tried Registry hack as suggested here but did not have any luck. Any idea how to achieve this?
For anyone who is facing the same issue, I solved it by using window.open. Instead of downloading the response, I directly pass the URL to window.open something like
window.open(apiUrl) // Exmp "host:api/documents/download?id=1"
Note:-API should returns the stream response with header type set. In my case C# web API method was
public HttpResponseMessage Download(int id)
{
var data = _service.Download(id);
HttpResponseMessage result = null;
result = Request.CreateResponse(HttpStatusCode.OK);
result.Content = new ByteArrayContent(data);//here data is byte[]
var name = data.Name.ToLower().Contains(data.DocType.ToLower())
? data.Name
: $"{data.Name}{data.DocType}";
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("inline")
{
FileName = name
};
result.Content.Headers.ContentType = new MediaTypeHeaderValue(MimeMapping.GetMimeMapping(name));
//here i am setting up the headers content type for example 'text/application-json'
return result;
}
Hope it will help someone.
I have a meteor application and in this one I get a base64 image. I want to save the image on a Digital Ocean instance, so I would convert it in a png or an other image format and send it to the server to get an url of the image.
But I didn't find a meteor package that does this.
Do you know how I can do that ?
I was running into a similar issue.
run the following:
meteor npm install --save file-api
This will allow the following code on the server for example:
import FileAPI from 'file-api';
const { File } = FileAPI;
const getFile = function(name,image){
const i = image.indexOf('base64,');
const buffer = Buffer.from(image.slice(i + 7), 'base64');
const file = new File({buffer: buffer, name, type: 'image/jpeg'});
return file;
}
Simply call it with any name of file you prefer, and the base64 string as the image parameter.
I hope this helps. I have tested this and it works on the server. I have not tested it on the client but I don't see why it wouldn't work.
I solved my problem using fs.writeFile from File System.
This is my javascript code on client side, I got a base64 image (img) from a plugin and when I click on my save button, I do this :
$("#saveImage").click(function() {
var img = $image.cropper("getDataURL")
preview.setAttribute('src', img);
insertionImage(img);
});
var insertionImage = function(img){
//some things...
Meteor.call('saveTileImage', img);
//some things...
}
And on the server side, I have :
Meteor.methods({
saveTileImage: function(fileData) {
var fs = Npm.require('fs');
var path = process.env.PWD + '/var/uploads/';
base64Data = fileData.replace(/^data:image\/png;base64,/, "");
base64Data += base64Data.replace('+', ' ');
binaryData = new Buffer(base64Data, 'base64').toString('binary');
var imageName = "tileImg_" + currentTileId + ".png";
fs.writeFile(path + imageName, binaryData, "binary", Meteor.bindEnvironment(function (err) {
if (err) {
throw (new Meteor.Error(500, 'Failed to save file.', err));
} else {
insertionTileImage(imageName);
}
}));
}
});
var insertionTileImage = function(fileName){
tiles.update({_id: currentTileId},{$set:{image: "upload/" + fileName}});
}
So, the meteor methods saveTileImage transform the base64 image into a png file and insertionTileImage upload it to the server.
BlobUrl, would it be a better option for you?
Save the images to a server as you like in base64 or whatever, and then when you are viewing the image on a page, generate the blobUrl of it. The url being used only at that time, preventing others from using your url on various websites and not overloading your image server ...
The case:
On Salesforce platform I use Google Drive to store files (images for this case) with configured Apex Google Drive API Framework. So Google Drive API handles authToken and so on. I can upload and browse images in my application. In my case I want to select multiple files and download them in a single zip file. So far I'm trying to do that using JSZip and FileSaver libraries. With the same code below I can zip and download multiple files stored somewhere else with proper response header, but not from GDrive because of CORS error.
https://xxx.salesforce.com/contenthub/download/XXXXXXXXXX%3Afile%XXXXXX_XXXXXXXXX. No'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://xxx.visual.force.com' is therefore not allowed access. If I just click on this link, file starts to download.
Is there any way to configure GDrive to enable response header: Access-Control-Allow-Origin: * or Access-Control-Allow-Origin: https://*/mydomain.com somehow or I just have to use something else, maybe server side compression? Now I am using the download link provided by Apex Google Drive API (looks like this:
https://xxx.salesforce.com/contenthub/download/XXXXXXXXXXX%3Afile%XXXXXXXX), it works fine when used as src="fileURL" or when pasted directly to the browser. GDrive connector add 'accesToken' and so on.
My code:
//ajax request to get files using JSZipUtils
let urlToPromise = (url) =>{
return new Promise(function(resolve, reject) {
JSZipUtils.getBinaryContent(url, function (err, data) {
if(err) {
reject(err);
} else {
resolve(data);
}
});
});
};
this.downloadAssets = () => {
let zip = new JSZip();
//here 'selectedAssets' array of objects each of them has 'assetFiles'
//with fileURL where I have url. Download and add them to 'zip' one by one
for (var a of this.selectedAssets){
for (let f of a.assetFiles){
let url = f.fileURL;
let name = a.assetName + "." + f.fileType;
let filename = name.replace(/ /g, "");
zip.file(filename, urlToPromise(url), {binary:true});
}
}
//generate zip and download using 'FileSaver.js'
zip.generateAsync({type:"blob"})
.then(function callback(blob) {
saveAs(blob, "test.zip");
});
};
I also tried to change let url = f.fileURL to let url = f.fileURL + '?alt=media'; and &access_token=CURRENT_TOKEN added by GDrive connector.
this link handled by GRDrive connector so if I just enter it in browser it download the image. However, for multiple download using JS I got CORS error.
I think this feature is not yet supported. If you check the Download Files guide from Drive API, there's no mention of downloading multiple files at once. That's because you have to make individual API requests for each file. This is confirmed in this SO thread.
But that selected multiple files are convert into single zip file and download that single zip file which is possible with google drive API. So how can i convert them into single Zip File? please tell me.
According to me, just download all files and store them at temporary directory location and then add that directory to zip file and store that zip to physical device.
public Entity.Result<Entity.GoogleDrive> DownloadMultipleFile(string[] fileidList)
{
var result = new Entity.Result<Entity.GoogleDrive>();
ZipFile zip = new ZipFile();
try
{
var service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Download File",
});
FilesResource.ListRequest listRequest = service.Files.List();
//listRequest.PageSize = 10;
listRequest.Fields = "nextPageToken, files(id, name, mimeType, fullFileExtension)";
IList<File> files = listRequest.Execute().Files;
if (files != null && files.Count > 0)
{
foreach (var fileid in fileidList)
{
foreach (var file in files)
{
if (file.Id == fileid)
{
result.Data = new Entity.GoogleDrive { FileId = fileid };
FilesResource.GetRequest request = service.Files.Get(fileid);
request.ExecuteAsync();
var stream = new System.IO.FileStream(HttpContext.Current.Server.MapPath(#"~\TempFiles") + "\\" + file.Name, System.IO.FileMode.Create, System.IO.FileAccess.Write);
request.MediaDownloader.ProgressChanged += (IDownloadProgress progress) =>
{
switch (progress.Status)
{
case DownloadStatus.Downloading:
{
break;
}
case DownloadStatus.Completed:
{
break;
}
case DownloadStatus.Failed:
{
break;
}
}
};
request.Download(stream);
stream.Close();
break;
}
}
}
}
zip.AddDirectory(HttpContext.Current.Server.MapPath(#"~\TempFiles"), "GoogleDrive");
string pathUser = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
string pathDownload = System.IO.Path.Combine(pathUser, "Downloads");
zip.Save(pathDownload + "\\GoogleDrive.zip");
System.IO.DirectoryInfo di = new System.IO.DirectoryInfo(HttpContext.Current.Server.MapPath(#"~\TempFiles"));
foreach (var file in di.GetFiles())
{
file.Delete();
}
result.IsSucceed = true;
result.Message = "File downloaded suceessfully";
}
catch (Exception ex)
{
result.IsSucceed = false;
result.Message = ex.ToString();
}
return result;
}
My previously published code works. Forgot to post a solution.
Just instead of using content hub link I started to use direct link to Google Drive and CORS issue was solved. Still not sure if CORS might be solved somehow at Salesforce side. Tried different setups with no luck.
Direct download link to GDrive works ok in my case. The only thing I had to change is the prefix to GDrive file ID.
Any help is most welcomed and really appreciated.
I have an MVC action which retries a file content from a web service. This action is invoked from a Angular service (located in services.js) using $http.post(action, model), and the action is returning a FileContentResult object, which contains the byte array and the content type.
public ActionResult DownloadResults(DownloadResultsModel downloadResultsModel)
{
downloadResult = ... // Retrieving the file from a web service
Response.ClearHeaders();
Response.AddHeader("Content-Disposition", string.Format("attachment; filename={0}", downloadResult.FileName));
Response.BufferOutput = false;
return new FileContentResult(downloadResult.Contents, downloadResult.ContentType);
}
The issue I'm having is about the browser not performing the default behavior of handing a file (for example, prompting to open it, saving it or cancel). The action is completed successfully with having the content of the file and the file name (injected to the FileContentResult object), but there s no response from the browser.
When I'm replacing the post with $window.location.href, and construct the URI myself, I'm hitting the action and after it completes the browser is handling the file as expected.
Does anyone can think of any idea how to complete the 'post' as expected?
Thanks,
Elad
I am using below code to download the file, given that the file does exist on the server and client is sending server the full path of the file...
as per you requirement change the code to specify path on server itself.
[HttpGet]
public HttpResponseMessage DownloadFile(string filename)
{
filename = filename.Replace("\\\\", "\\").Replace("'", "").Replace("\"", "");
if (!char.IsLetter(filename[0]))
{
filename = filename.Substring(2);
}
var fileinfo = new FileInfo(filename);
if (!fileinfo.Exists)
{
throw new FileNotFoundException(fileinfo.Name);
}
try
{
var excelData = File.ReadAllBytes(filename);
var result = new HttpResponseMessage(HttpStatusCode.OK);
var stream = new MemoryStream(excelData);
result.Content = new StreamContent(stream);
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = fileinfo.Name
};
return result;
}
catch (Exception ex)
{
return Request.CreateResponse(HttpStatusCode.ExpectationFailed, ex);
}
}
and then on client side in angular:
var downloadFile = function (filename) {
var ifr = document.createElement('iframe');
ifr.style.display = 'none';
document.body.appendChild(ifr);
ifr.src = document.location.pathname + "api/GridApi/DownloadFile?filename='" + escape(filename) + "'";
ifr.onload = function () {
document.body.removeChild(ifr);
ifr = null;
};
};
I am trying to use browserify to access a local binary file (that is, the binary file is in the same directory as the javascript file, which is in the user's computer).
I haven't succeeded. Here's what I tried and what I know:
~) I know fs won't work...
0) I tried using the require('html') but it says 'ajax not supported in this browser' [I am using chromium... but I'd assume it's roughly the same thing as chrome].
1) I tried using 'browser-request'. This reads the binary file... as a string.
It is based on 'request' so I should be able to configure the options, including encoding: null, which would solve all my problems but...looking at the source code, you'll see that no support for the encoding option is present. Not even a warning.
2) I used xmlhttprequest, which required the 'html' module... so again, I get the same error as in 0) Strangely enough, 'browser-request' uses this module and it works... and I have absolutely no idea why.
3) At this point, I looked into html5 file system support. It would work but I don't want the user to specify a file... seeing as I really ONLY want to get the buffer to memory. Is there any other way to access the file? Perhaps using --allow-file-access when starting chromium?
4) If all else fails, I just want a way to get the Buffer into my code. I guess I could just use node on shell and copy paste the result of reading the file into memory...
Is there any hope at all?
Here's what somewhat works:
function toArrayBuffer(buffer) {
var ab = new ArrayBuffer(buffer.length);
var view = new Uint8Array(ab);
for (var i = 0; i < buffer.length; ++i) {
view[i] = buffer[i];
}
return ab;
}
// node: readFileSync + toArrayBuffer
// browser: ajax http request
function readFile(filename, doneCb) {
var isNode =
typeof global !== "undefined" &&
global.toString() == '[object global]';
if (isNode) {
var fs = require('fs');
var buffer = fs.readFileSync(filename);
buffer = toArrayBuffer(buffer);
doneCb(buffer);
} else {
var http = require('http');
var buf;
var req = http.get({ path : '/'+ filename }, function (res) {
res.on('data', function (chunk) {
buf = chunk;
});
res.on('end', function () {
doneCb(buf);
});
});
req.xhr.responseType = 'arraybuffer';
}
}
It requires a server and I'm strugging with on how to make it work in testling.
Another approach I can think of is to use brfs with base64 encoding:
var base64 = fs.readFileSync('file.bin', enc='base64');
var buf = new Buffer(base64, 'base64');
var ab = toArrayBuffer(buf);
It is simpler, but it is not dynamic and cannot be refactored to self-contained function.
If it's not dynamic use brfs transform.