How to delete the file that was sent as StreamContent of HttpResponseMessage - c#

In ASP.NET webapi, I send a temporary file to client. I open a stream to read the file and use the StreamContent on the HttpResponseMessage. Once the client receives the file, I want to delete this temporary file (without any other call from the client)
Once the client recieves the file, the Dispose method of HttpResponseMessage is called & the stream is also disposed. Now, I want to delete the temporary file as well, at this point.
One way to do it is to derive a class from HttpResponseMessage class, override the Dispose method, delete this file & call the base class's dispose method. (I haven't tried it yet, so don't know if this works for sure)
I want to know if there is any better way to achieve this.

Actually your comment helped solve the question... I wrote about it here:
Delete temporary file sent through a StreamContent in ASP.NET Web API HttpResponseMessage
Here's what worked for me. Note that the order of the calls inside Dispose differs from your comment:
public class FileHttpResponseMessage : HttpResponseMessage
{
private string filePath;
public FileHttpResponseMessage(string filePath)
{
this.filePath = filePath;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
Content.Dispose();
File.Delete(filePath);
}
}

Create your StreamContent from a FileStream having DeleteOnClose option.
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(
new FileStream("myFile.txt", FileMode.Open,
FileAccess.Read, FileShare.None, 4096, FileOptions.DeleteOnClose)
)
};

I did it by reading the file into a byte[] first, deleting the file, then returning the response:
// Read the file into a byte[] so we can delete it before responding
byte[] bytes;
using (var stream = new FileStream(path, FileMode.Open))
{
bytes = new byte[stream.Length];
stream.Read(bytes, 0, (int)stream.Length);
}
File.Delete(path);
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
result.Content = new ByteArrayContent(bytes);
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
result.Content.Headers.Add("content-disposition", "attachment; filename=foo.bar");
return result;

Related

FileStreamResult - The process cannot access the file, because it is being used by another process

ASP .NET Core
MVC Controller - download file from server storage using FileStream and returning FileStreamResult
public IActionResult Download(string path, string fileName)
{
var fileStream = System.IO.File.OpenRead(path);
return File(fileStream, "application/force-download", fileName);
}
Everything works fine, but once the user cancels downloading before the download is complete, other actions in the controller working with this file (Delete file, rename file) do not work because: The process cannot access the file, because it is being used by another process
FileStream automatically dispose when the file download is complete, but for some reason it does not terminate when the user terminates the download manually.
I have to restart the web application => the program that uses the file is IISExpress
Does anyone please know how to dispose stream if the user manually ends the download?
EDIT:
FileStream stream = null;
try
{
using (stream = System.IO.File.OpenRead(path))
{
return File(stream, "application/force-download", fileName);
}
}
Code that I tried to end the Stream after returning FileStreamResult, I am aware that it can not work, because after return File (stream, contentType, fileName) it immediately jumps to the block finally and the stream closes, so the download does not start because the stream is closed
It seems the source of the FileStreamResult class shows it has no support for cancellation.
You will need to implement your own, if required. E.g. (not-tested, just imagined)
using System.IO;
namespace System.Web.Mvc
{
public class CancellableFileStreamResult : FileResult
{
// default buffer size as defined in BufferedStream type
private const int BufferSize = 0x1000;
private readonly CancellationToken _cancellationToken;
public CancellableFileStreamResult(Stream fileStream, string contentType,
CancellationToken cancellationToken)
: base(contentType)
{
if (fileStream == null)
{
throw new ArgumentNullException("fileStream");
}
FileStream = fileStream;
_cancellationToken = cancellationToken;
}
public Stream FileStream { get; private set; }
protected override void WriteFile(HttpResponseBase response)
{
// grab chunks of data and write to the output stream
Stream outputStream = response.OutputStream;
using (FileStream)
{
byte[] buffer = new byte[BufferSize];
while (!_cancellationToken.IsCancellationRequested)
{
int bytesRead = FileStream.Read(buffer, 0, BufferSize);
if (bytesRead == 0)
{
// no more data
break;
}
outputStream.Write(buffer, 0, bytesRead);
}
}
}
}
}
You can then use it like
public IActionResult Download(string path, string fileName, CancellationToken cancellationToken)
{
var fileStream = System.IO.File.OpenRead(path);
var result = new CancellableFileStreamResult(
fileStream, "application/force-download", cancellationToken);
result.FileDownloadName = fileName;
return result;
}
Again, I'm this is not tested, just imagined.
Maybe this doesn't work, as the action is already finished, thus cannot be cancelled anymore.
EDIT:
The above answer "Imagined" for ASP.net framework. ASP.net core has a quite different underlying framework: In .net core, the action is processed by and executor, as shown in the source. That will eventually call WriteFileAsync in the FileResultHelper. There you can see that StreamCopyOperation is called with the cancellationToken context.RequestAborted. I.e. cancellation is in place in .net Core.
The big question is: why isn't the request aborted in your case.

Download ZipArchive from c# web api method returns "net::ERR_CONNECTION_RESET" in chrome

I want to call a web api method and have it allow the user to download a zip file that I create in memory. I also want to create the entries in memory as well.
I'm having trouble getting the server to correctly output the download.
Here is my web api method:
[HttpGet]
[Route("api/downloadstaffdata")]
public HttpResponseMessage DownloadStaffData()
{
var response = new HttpResponseMessage(HttpStatusCode.OK);
using (var stream = new MemoryStream())
{
using (var archive = new ZipArchive(stream, ZipArchiveMode.Create, true))
{
//future for loop to create entries in memory from staff list
var entry = archive.CreateEntry("bob.txt");
using (var writer = new StreamWriter(entry.Open()))
{
writer.WriteLine("Info for: Bob");
}
//future add staff images as well
}
stream.Seek(0, SeekOrigin.Begin);
response.Content = new StreamContent(stream);
}
response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
{
FileName = "staff_1234_1.zip"
};
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/zip");
return response;
}
Here is my calling js code:
window.open('api/downloadstaffdata');
Here is the response from Chrome:
net::ERR_CONNECTION_RESET
I don't know what I'm doing wrong. I've already searched SO and read the articles about creating the zip file, but I can't get passed the connection reset error when trying to return the zip archive to the client.
Any ideas?
You have your memory stream inside a using block. As such, your memory stream are being disposed before your controller has the chance to write it out (hence the ERR_CONNECTION_RESET).
A MemoryStream does not need to be disposed explicitly (its various derived type may need to be, but not the MemoryStream itself). Garbage Collector can clean it up automatically.

Mvc - How to stream large file in 4k chunks for download

i was following this example, but when download starts it hangs and than after a minute it shows server error. I guess response end before all data id sent to client.
Do you know another way that i can do this or why it's not working?
Writing to Output Stream from Action
private void StreamExport(Stream stream, System.Collections.Generic.IList<byte[]> data)
{
using (BufferedStream bs = new BufferedStream(stream, 256 * 1024))
using (StreamWriter sw = new StreamWriter(bs))
{
foreach (var stuff in data)
{
sw.Write(stuff);
sw.Flush();
}
}
}
Can you show the calling method? What is the Stream being passed in? Is it the Response Stream?
There are many helpful classes to use that you don't have to chuck yourself because they chunk by default. If you use StreamContent there is a constructor overload where you can specify buffer size. I believe default is 10kB.
From memory here so it my not be complete:
[Route("download")]
[HttpGet]
public async Task<HttpResponseMessage> GetFile()
{
var response = this.Request.CreateResponse(HttpStatusCode.OK);
//don't use a using statement around the stream because the framework will dispose StreamContent automatically
var stream = await SomeMethodToGetFileStreamAsync();
//buffer size of 4kB
var content = new StreamContent(stream, 4096);
response.Content = content;
return response;
}

Web API download locks file

I'm having a small issue with a WebAPI method that downloads a file when the user calls the route of the method.
The method itself is rather simple:
public HttpResponseMessage Download(string fileId, string extension)
{
var location = ConfigurationManager.AppSettings["FilesDownloadLocation"];
var path = HttpContext.Current.Server.MapPath(location) + fileId + "." + extension;
var result = new HttpResponseMessage(HttpStatusCode.OK);
var stream = new FileStream(path, FileMode.Open);
result.Content = new StreamContent(stream);
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
return result;
}
The method works as expected - the first time I call it. The file is transmitted and my browser starts downloading the file.
However - if I call the same URL again from either my own computer or from any other - I get an error saying:
The process cannot access the file
'D:\...\App_Data\pdfs\test-file.pdf' because it is being used by
another process.
This error persists for about a minute - and then I can download the file again - but only once - and then I have to wait another minute or so until the file is unlocked.
Please note that my files are rather large (100-800 MB).
Am I missing something in my method? It almost seems like the stream locks the file for some time or something like that?
Thanks :)
It is because your file is locked by the first stream, you must specify a FileShare that allow it to be opened by multiple streams :
public HttpResponseMessage Download(string fileId, string extension)
{
var location = ConfigurationManager.AppSettings["FilesDownloadLocation"];
var path = HttpContext.Current.Server.MapPath(location) + fileId + "." + extension;
var result = new HttpResponseMessage(HttpStatusCode.OK);
var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
result.Content = new StreamContent(stream);
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
return result;
}
Like that you allow multiple stream to open this file for read only.
See the MSDN documentation on that constructor overload.

Deserializing an array using SOAP

I'm having a problem getting a SOAP service to deserialize an array of objects; its only reading in the first member of the array and nothing else. Here is my code:
public void StoreCredentials(Credentials credentials)
{
Credentials[] credsArray;
var soap = new SoapFormatter();
var stream = new FileStream(_path, FileMode.OpenOrCreate);
try
{
credsArray = (Credentials[])soap.Deserialize(stream);
var credsList = credsArray.ToList();
credsList.Add(credentials);
credsArray = credsList.ToArray();
}
catch (SerializationException)
{
credsArray = new[] {credentials};
}
soap.Serialize(stream, credsArray);
stream.Close();
}
I wrote a simple unit test which adds two Credentials objects to the file, the output looks correct, both sets of credentials are present, but when I run the test to add a third set to the file, the soap.Deserialize(stream) line returns an array with only one entry, even though the file its reading from contains two entries. Am I doing something wrong here? Is there a better / easier way to do this? Please help!
I figured it out. The problem is with the FileMode.OpenOrCreate attribute of the FileStream. This will open the file but append it with a new collection instead of altering the original collection. I needed to overwrite the file by using FileMode.Create instead, so here is the working code (I also changed the collection to a Hashtable, which works better):
public void StoreCredentials(Credentials credentials)
{
credentials.Encrypt(_myUser.Encryption);
Hashtable credsTable;
var soap = new SoapFormatter();
FileStream stream;
try
{
stream = new FileStream(_path, FileMode.Open, FileAccess.ReadWrite);
credsTable = (Hashtable) soap.Deserialize(stream);
stream.Close();
stream = new FileStream(_path, FileMode.Create, FileAccess.ReadWrite);
if (credsTable.ContainsKey(credentials.Id))
credsTable[credentials.Id] = credentials;
else
credsTable.Add(credentials.Id, credentials);
}
catch (FileNotFoundException e)
{
stream = new FileStream(_path, FileMode.Create, FileAccess.ReadWrite);
credsTable = new Hashtable {{credentials.Id, credentials}};
}
soap.Serialize(stream, credsTable);
stream.Close();
}

Categories

Resources