C# MVC Download Big File from S3 Async - c#

I have to download a file from aws S3 async. I have a anchor tag, on clicking it a method will be hit in a controller for download. The file should be start downloading at the bottom of the browser, like other file download.
In View
Click here
In Controller
public void action()
{
try
{
AmazonS3Client client = new AmazonS3Client(accessKeyID, secretAccessKey);
GetObjectRequest req = new GetObjectRequest();
req.Key = originalName;
req.BucketName = ConfigurationManager.AppSettings["bucketName"].ToString() + DownloadPath;
FileInfo fi = new FileInfo(originalName);
string ext = fi.Extension.ToLower();
string mimeType = ReturnmimeType(ext);
var res = client.GetObject(req);
Stream responseStream = res.ResponseStream;
Stream response = responseStream;
return File(response, mimeType, downLoadName);
}
catch (Exception)
{
failure = "File download failed. Please try after some time.";
}
}
The above function makes the browser to load until the file is fully downloaded. Then only the file is visible at the bottom. I cant see the how mb is downloading.
Thanks in advance.

You must send ContentLength to client in order to display a progress. Browser has no information about how much data it will receive.
If you look at source of FileStreamResult class, used by File method, it does not inform client about "Content-Length". https://aspnetwebstack.codeplex.com/SourceControl/latest#src/System.Web.Mvc/FileStreamResult.cs
Replace this,
return File(response, mimeType, downLoadName);
with
return new FileStreamResultEx(response, res.ContentLength, mimeType, downloadName);
public class FileStreamResultEx : ActionResult{
public FileStreamResultEx(
Stream stream,
long contentLength,
string mimeType,
string fileName){
this.stream = stream;
this.mimeType = mimeType;
this.fileName = fileName;
this.contentLength = contentLength;
}
public override void ExecuteResult(
ControllerContext context)
{
var response = context.HttpContext.Response;
response.BufferOutput = false;
response.Headers.Add("Content-Type", mimeType);
response.Headers.Add("Content-Length", contentLength.ToString());
response.Headers.Add("Content-Disposition","attachment; filename=" + fileName);
using(stream) {
stream.CopyTo(response.OutputStream);
}
}
}
Alternative
Generally this is a bad practice to download and deliver S3 file from your server. You will be charged twice bandwidth on your hosting account. Instead, you can use signed URLs to deliver non public S3 objects, with few seconds of time to live. You could simply use Pre-Signed-URL
public ActionResult Action(){
try{
using(AmazonS3Client client =
new AmazonS3Client(accessKeyID, secretAccessKey)){
var bucketName =
ConfigurationManager.AppSettings["bucketName"]
.ToString() + DownloadPath;
GetPreSignedUrlRequest request1 =
new GetPreSignedUrlRequest(){
BucketName = bucketName,
Key = originalName,
Expires = DateTime.Now.AddMinutes(5)
};
string url = client.GetPreSignedURL(request1);
return Redirect(url);
}
}
catch (Exception)
{
failure = "File download failed. Please try after some time.";
}
}
As long as object do not have public read policy, objects are not accessible to users without signing.
Also, you must use using around AmazonS3Client in order to quickly dispose networks resources, or just use one static instance of AmazonS3Client that will reduce unnecessary allocation and deallocation.

As i understand, you want to make something like "reverse proxy" from your server to S3. Very userful article how to do that with Nginx you can find here: https://stackoverflow.com/a/44749584

Related

How to upload file to Azure DataLake through API?

Net core application. I am trying to upload file to data lake through API. I have below controller method which accepts file.
[HttpPost]
public async Task<IActionResult> Upload(IFormFile files)
{
var path = Path.Combine(Directory.GetCurrentDirectory(), "Uploads", files.FileName);
var stream = new FileStream(path, FileMode.Create);
string containerName = "raw";
DataLakeServiceClient dataLakeServiceClient = _dataLakeRepository.GetDataLakeServiceClient("test");
DataLakeFileSystemClient dataLakeFileSystemClient = _dataLakeRepository.GetFileSystem(dataLakeServiceClient, containerName);
await _dataLakeRepository.UploadFile(dataLakeFileSystemClient, "directory2", "text1.txt", stream);
return Ok();
}
I have below DataLake method which will upload file to data lake.
public async Task UploadFile(DataLakeFileSystemClient fileSystemClient, string directoryName, string fileName, Stream content)
{
DataLakeDirectoryClient directoryClient = fileSystemClient.GetDirectoryClient(directoryName);
DataLakeFileClient fileClient = await directoryClient.CreateFileAsync(fileName);
long fileSize = content.Length;
await fileClient.AppendAsync(content, offset: 0);
await fileClient.FlushAsync(position: fileSize);
}
Below method to get file system client
public DataLakeFileSystemClient GetFileSystem(DataLakeServiceClient serviceClient, string FileSystemName)
{
return serviceClient.GetFileSystemClient(FileSystemName);
}
I tried to upload file and In below line
await fileClient.AppendAsync(content, offset: 0);
I got below error
Azure.RequestFailedException: The value for one of the HTTP headers is not in the correct format.
Status: 400 (The value for one of the HTTP headers is not in the correct format.)
ErrorCode: InvalidHeaderValue
Also when I debug I see content.Length is also zero. I think I am missing something in stream because I am having some issue with stream. I am not able to figure out the issue. Can someone help me to fix this. Any help would be appreciated. Thanks
After read official doc, we can find the sample code use FileStream.
So you should convert Stream to FileStream .

How to Cancel Inprogress Download in .net core web api using file result?

I have react which calls my api to download file. While downloading I want it to show the progress. Suppose the user wants to stop the download they have to cancel it. However, even if I cancel from UI it continues to download from the server side code. I have attached my source code and I want to know how to stop the download from the server.
[HttpGet]
[Route("[action]")]
public FileResult DownloadPackageFile(string packageID)
{
log.Debug("DownloadPackageFile Starts");
// Create HTTP Response.
HttpResponseMessage response = new HttpResponseMessage();
response.StatusCode = HttpStatusCode.OK;
try
{
log.Debug("DownloadPackageFile PackageId " + packageID);
//File Extension
string fileName = packageID + ".zip";
// Set the File Path.
string filePath = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location), SolutionApiConstants.SolutionRepo, fileName);
log.Debug("DownloadPackageFile : File Path " + filePath);
// Check whether File exists.
if (!System.IO.File.Exists(filePath))
{
//Throw 404 (Not Found) exception if File not found.
response.StatusCode = HttpStatusCode.NotFound;
response.ReasonPhrase = string.Format("File not found: {0} .", packageID);
return null;
//throw new HttpResponseException(response);
}
byte[] bytes = System.IO.File.ReadAllBytes(filePath);
return File(bytes, System.Net.Mime.MediaTypeNames.Application.Zip, fileName); // ZIP

Return File not forcing download of file in .NET core API

I have a .NET Core API trying to return a file for the browser to download when triggered.
[HttpGet("DownloadFile/{fileId}")]
public async Task<ActionResult> DownloadFile(string fileId, CancellationToken cancellationToken = default)
{
var file = await _fileUploadService.DownloadFile(Guid.Parse(fileId), _ambientState.UserId);
[![enter image description here][1]][1]
var path = Path.Combine(
Directory.GetCurrentDirectory(),
"fileuploads", file.FilePath);
byte[] fileBytes = System.IO.File.ReadAllBytes(path);
return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, "event-doc" + Path.GetExtension(path));
}
But this doesn't force the download of the file I have tried many things but nothing causes the file to be downloaded by the browser. There are no errors either. The call to the API succeeds but no file is downloaded
You'll need to add a content-disposition header before you return the action result.
var header = new ContentDispositionHeaderValue("attachment")
{
FileName = document.FileName
};
Response.Headers.Add(HeaderNames.ContentDisposition, header.ToString());

Google drive api v3 response null

I want to try upload file google drive and my code is here:
public static File UploadFile(DriveService service, string fileName, string filePath, string description, string parent)
{
var fileMetadata = new File
{
Name = fileName,
MimeType = GetMimeType(fileName),
Description = description,
OriginalFilename = fileName,
};
FilesResource.CreateMediaUpload request;
using (var stream = new System.IO.FileStream(filePath, System.IO.FileMode.Open))
{
request = service.Files.Create(fileMetadata, stream, GetMimeType(fileName));
request.Fields = "id";
request.Alt=FilesResource.CreateMediaUpload.AltEnum.Json;
request.Upload();
}
var file = request.ResponseBody;
Console.WriteLine("File ID: " + file.Id);
return null;
}
But Response body is always null and i cant upload. By the way i saw error in this picture:
google-api-dotnet-client 1.16.0.0 gzip the format of the invalid value
Anyone idea how to solve this problem and upload files google drive.
It appears V3 has replaced InsertMediaUpload for CreateMediaUpload and when using a service account at least the ResponseBody will always be returned as NULL.
This makes it difficult to determine the ID of the file and later find it to modify permissions or do anything else.

Return file from ASP.NET 5 Web API

My previous question: How to return file from ASP.net 5 web api
I am trying to return a file as the response from Web API POST request.
I'm using dnx451 framework and rc1-final build. Controller method:
[HttpPost("")]
public ActionResult Post([FromBody]DocumentViewModel vm)
{
try
{
if (ModelState.IsValid)
{
var Document = _repository.GetDocumentByGuid(vm.DocumentGuid, User.Identity.Name);
var Params = Helper.ClientInputToRealValues(vm.Parameters, Document.DataFields);
var file = Helper.GeneratePdf(Helper.InsertValues(Params, Document.Content));
FileStream stream = new FileStream(file,FileMode.Open);
return File(stream, "application/pdf", "test.pdf");
}
}
catch (Exception ex)
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return null;
}
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return null;
}
As result I get a file with name "response". After saving it as pdf i try to open it, and it says it is damaged. Hope you can help me. I am using Postman as test client.
Thanks
Please see my answer in the other post: Return file as response
For reference, I think this fits your needs:
public FileResult TestDownload()
{
HttpContext.Response.ContentType = "application/pdf";
FileContentResult result = new FileContentResult(System.IO.File.ReadAllBytes("YOUR PATH TO PDF"), "application/pdf")
{
FileDownloadName = "test.pdf"
};
return result;
}
I've just had this problem and found a solution. As long as you have an absolute path to your file, then you can return a PhysicalFileResult and explicitly set the Content-Disposition header on the Response, like so:
[HttpGet("{key}")]
public IActionResult Get(string key)
{
var file = _files.GetPath(key);
var result = PhysicalFile(file.Path, "text/text");
Response.Headers["Content-Disposition"] = new ContentDispositionHeaderValue("attachment")
{
FileName = file.Name
}.ToString();
return result;
}
Using PhysicalFile also has the advantage that all the asynchronous streaming of bytes and so on is taken care of by the framework.
It's perhaps better to consider the use the FileStreamResult action result.
This has the advantage of not requiring that the entire file contents be held in memory which, depending on the size of the file, level of traffic, etc. could very easily lead to problems of scale.
Something like this:
[HttpGet]
public FileStreamResult DownloadFile()
{
var Document = _repository.GetDocumentByGuid(vm.DocumentGuid, User.Identity.Name);
var Params = Helper.ClientInputToRealValues(vm.Parameters, Document.DataFields);
var file = Helper.GeneratePdf(Helper.InsertValues(Params, Document.Content));
var stream = new FileStream(file,FileMode.Open);
return new FileStreamResult(stream, "application/pdf")
{
FileDownloadName = "test.pdf"
};
}

Categories