I'm storing large media files in Azure Blob Storage (audio, images, videos) that need to be previewed on my web client application.
Currently the client requests a media file and my server downloads the entire blob to memory, then returns the file to the client.
Controller Action
[HttpGet("[action]/{blobName}")]
public async Task<IActionResult> Audio(string blobName)
{
byte[] byteArray = await _blobService.GetAudioAsync(blobName);
return File(byteArray, AVHelper.GetContentType(blobName));
}
Download Service Method
private async Task<byte[]> GetAudioAsync(CloudBlobContainer container, string blobName)
{
using (MemoryStream stream = new MemoryStream())
{
CloudBlockBlob blob = container.GetBlockBlobReference(blobName);
await blob.DownloadToStreamAsync(stream);
return stream.ToArray();
}
}
I'm concerned that this is not good design as the file is being downloaded twice in serial which would cause slower downloads and heightened server memory usage. File sizes can be several hundred MB.
Is there some recommended method for doing this? Maybe something where the server is downloading from blob storage and streaming the file to the client pseudo simultaneously? So the client doesn't have to wait for the server to completely download the file to start its download, and the server can remove already transmitted file contents from memory.
To make the answer visible to others, I'm summarizing the answer shared in comment:
The suggestion is to redirect to the Blob Url directly so that the file download can start to client machine directly and the web application don't need to download it to stream or file on the server. Steps:
1.When client clicks on Download, an AJAX request comes to the server.
2.the server code performs necessary verification and returns the file URL of Azure Storage.
3.The AJAX code get the URL returned from the server and opens up a new browser window and redirects it to the URL.
Related
I have a shared webhosting service which my ASP.NET Core app runs, and an FTP server. I want to serve the files that clients need to download from the site.
I want the files not to be accessible to everyone, so this is what I do (I use FluentFTP):
var cred = new NetworkCredential(_config.UserName, _config.Password);
FtpClient client = new FtpClient(_config.Host, int.Parse(_config.Port), cred);
await client.ConnectAsync();
var remotePath = $"{_config.Folder}{FILE_DIR}/{filename}";
var result = await client.DownloadAsync(remotePath: remotePath, 0L);
await client.DisconnectAsync();
if (result != null)
{
return File(result, "application/force-download", filename)
}
else
{
throw new Exception("Failed");
}
The problem is, the server tries to download the file from the FTP server, then sends it to client. And there is the same problem for upload too, the client needs to upload file to the server, and then server uploads it to the FTP server. I think this can be very time-consuming with large files.
Is there any better way to achieve this goal? Or is it possible to write the file being downloaded from FTP simultaneously to the client response so the client downloads any bit of file downloaded in server and doesn't have to wait for the download to server to finish to start the download?
Thanks in advance.
Download the file directly to the HTTP output stream with use of FtpClient.Download (to be renamed FtpClient.DownloadStream in upcoming versions) and HttpResponse.OutputStream:
Response.AddHeader("Content-Disposition", $"attachment; filename={filename}");
client.Download(Response.OutputStream, remotePath);
(where Response is ASP.NET HttpResponse).
A similar question for native .NET FtpWebRequest/WebClient:
Download file from FTP and how prompt user to save/open file in ASP.NET C#
Also note that the application/force-download seems like a hack.
Use Content-Disposition: attachment to force the download.
I want to save a file from a http link to the local drive just temporarily in order to access it, this one is working so far and I'm getting the data but a need to write this data to a local file, for example to C:\Windows\temp\test.text, this file should be deleted afterwards.
WebClient client = new WebClient();
string url = "http://www.example.com/test.text";
var file = client.DownloadData(url);
could any one help me on this, thank you!
You cannot write a file on client machine due to security, Any program executing in the browser executes within the browser sandbox and has access to limited features like printer, cookies, etc.
You can write the data to file as a Response object to the client's browser. The Client has the choice of whether to save it or not to his machine.
I have two cloud provider with their client SDKs say SDK1 and SDK2. And I wanted to copy one file from one cloud to another cloud storage. These SDKs have upload and download APIs like this:
Response uploadAsync(Uri uploadLocation, Stream fileStream);
Stream downloadAsync(Uri downloadLocation);
Earlier I was copying downloaded Stream to MemoryStream and passing it to upload API. And it was working but obviously, it will load entire file to memory which is not good.
I cannot directly pass downloaded Stream to upload API as somewhere it's checking Length of Stream and System.Net.ConnectStream being non-seekable throws Exception.
Any pointer on how can we use the downloaded Stream (which is of Type System.Net.ConnectStream) in upload API without actually storing entire file?
I have a unique scenario in which I'd like the end result to help me upload a zip file. Here is what is happening in my workflow:
Our user is given an application on their local machine. With a click of a button, it will copy files and a zip file to remote-machine-1.
On remote-machine-2, it is running a .NET Core web app.
On remote-machine-1, I'd like to ping an endpoint off the web app in order to upload the zip file to remote-machine-2. However, the caveat is that the user will not be able to specify where this zip file is - the location of the zip file already known due to the structure of how the files and zip file are copied over in the first place.
So the question remains, with the code below - how do I pass in an IFormFile object when I call the endpoint localhost:5000/PublishTargetAsync?file=[???]? Or is there another workaround?
public async Task<bool> PublishTargetAsync(IFormFile file)
{
if (file != null)
{
using (var fileStream = new FileStream(Path.Combine(_targetOutputDirectory.ToFileSystemPath(), file.Name), FileMode.Create))
{
await file.CopyToAsync(fileStream);
}
}
return true;
}
A simple, but non optimized approach would be to use HttpClient and post the file contents as a base64 encoded string as Json using sample code similar to what is in my link. From there you could work your way back to using HttpWebRequests and a network stream and crafting the Http request by hand if necessary for performance, but the above approach should work for most small files. You'll have to modify your PublishTargetAsync endpoint to handle a post request with the right type.
I have a Windows Service will be reading from local disk (video files) and post them to remote service via API.
Video files over 2gb size and I need to transfer them to another location through HttpClient/POST request.
There is no limitation on API call, so even if file is 10gb I can still post file to destination.
Right now I am reading entire video file as byte[] fileContent and pass it to function as
ByteArrayContent contentBody = new ByteArrayContent(fileContent);
It works for now, but since this is not scalable. (If multiple files will be transferred at the same time, it can fill up memory) I am seeking for a solution that transfer will happen in chunks.
Question: Can I read big files in buffer and transfer over HTTP as I am reading from local disk?
You can use the PostAsync(Uri, HttpContent) method of HttpClient. In order to stream the contents of your local file, use the StreamContent subclass of HttpContent and supply a file reader stream. A brief example:
async Task PostBigFileAsync(Uri uri, string filename)
{
using (var fileStream = File.OpenRead(filename))
{
var client = new HttpClient();
var response = await client.PostAsync(uri, new StreamContent(fileStream));
}
}