Unable to delete file after HttpActionResult in WebAPI controller - c#

I have an async controller method that does some heavy lifting when called, creates folders, zips files, and combines it into a final archive that is returned to the client via download. The problem is, I need to delete the files when finished but I'm getting an error during the delete:
The process cannot access the file 'Archive.zip' because it is being used by another process.
I understand the problem, but I can't figure out how to resolve it.
Here's some code snippets:
First, I created a custom filter:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class DeleteFileAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext filterContext)
{
// Delete file
HttpContext.Current.Response.Flush();
HttpContext.Current.Response.End();
var path = new DirectoryInfo(Library.Utilities.Files.MapPath("~/archives"));
foreach (var dir in path.GetDirectories())
{
dir.Delete(true);
}
base.OnActionExecuted(filterContext);
}
}
Here's my main controller entry point which I decorated with that custom filter:
[DeleteFile]
[HttpGet]
public async Task<IHttpActionResult> Download(string boardId, string boardName)
{
// I do a bunch of file related stuff, and then:
return new FileActionResult(boardName + ".zip", zipPath);
}
And here is my FileActionResult class:
public class FileActionResult : IHttpActionResult
{
public FileActionResult(string filename, string path)
{
Filename = filename;
Path = path;
}
public string Filename { get; }
public string Path { get; }
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
HttpContext.Current.Response.Clear();
var response = new HttpResponseMessage {Content = new StreamContent(File.OpenRead(Path))};
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-zip-compressed");
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = Filename
};
return Task.FromResult(response);
}
}
I assumed that by flushing and ending the response within the action filter prior to deleting the files would help, but it does not.

Try disposing of the stream content:
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
HttpContext.Current.Response.Clear();
HttpResponseMessage response = null;
using (var stream = new File.OpenRead(path))
{
response = new HttpResponseMessage { Content = new StreamContent(stream)};
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-zip-compressed");
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = Filename
};
}
return Task.FromResult(response);
}

Related

FromForm IFormFile doesn't work in controller

I'm try receive a file in controller, but he always come without values.
Controller:
[ApiController, Route("api/[controller]")]
public class BlobController : ControllerBase
{
[HttpPost]
[Consumes("multipart/form-data")]
public async Task<ActionResult<FileUploadResponses>> Post([FromForm]IEnumerable<IFormFile> formsfiles)
{
List<FileUploadResponse> fileResponseList = new();
...
return Ok(new FileUploadResponses { Files= fileResponseList });
}
}
Image Controller:
I'm trying with two ways:
PostMan:
Blazor HttpClient (follow example MS https://learn.microsoft.com/pt-br/aspnet/core/blazor/file-uploads?view=aspnetcore-6.0&pivots=webassembly):
private async Task UploadFiles(IEnumerable<IBrowserFile> files)
{
FileUploadResponses uploadResponses = new();
using (var content = new MultipartFormDataContent())
{
foreach (var file in files)
{
var fileContent = new StreamContent(file.OpenReadStream(MaxFileSize));
fileContent.Headers.ContentType = new MediaTypeHeaderValue(file.ContentType);
content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name
);
}
var response = await Client.PostAsync("/api/blob", content);
uploadResponses = await response.Content.ReadFromJsonAsync<FileUploadResponses>() ?? new();
}
}
does anyone have a solution to recieve the [FromForm]IEnumerable<IFormFile> with values?
While posting, the data cannot be found because the parameter name is written as file, you should try it as formFiles in postman.

Exported file is returning as JSON instead of Excel file

I'm trying to return an Excel file but its always trying to save a JSON file instead of Excel file. Can you tell me what is wrong with this code? I thought the content type was wrong but I tried several options there and yet still returning a JSON...
/Excel builder
public class ExcelFileBuilder
{
public byte[] BuildEmployeeRecordFile(IEnumerable<EmployeeRecordDto> records)
{
byte[] fileContents;
ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
using (var package = new ExcelPackage())
{
var worksheet1 = package.Workbook.Worksheets.Add("TEST");
//ADD data here
// Export it to byte array
fileContents = package.GetAsByteArray();
}
return fileContents;
}
/Query class
public class ExportEmployeeRecordQuery : IRequest<ExportEmployeeRecordVm>
{
}
public class ExportEmployeeRecordQueryHandler : IRequestHandler<ExportEmployeeRecordQuery, ExportEmployeeRecordVm>
{
private readonly IApplicationDbContext _context;
private readonly IExcelFileBuilder _fileBuilder;
public ExportEmployeeRecordQueryHandler(IApplicationDbContext context, IExcelFileBuilder fileBuilder)
{
_context = context;
_fileBuilder = fileBuilder;
}
public async Task<ExportEmployeeRecordVm> Handle(ExportEmployeeRecordQuery request, CancellationToken cancellationToken)
{
var vm = new ExportEmployeeRecordVm();
//get data from db here
vm.Content = _fileBuilder.BuildEmployeeRecordFile(null); //pass null for now
vm.ContentType = "application/octet-stream";
vm.FileName = "TestFile.xlsx";
return await Task.FromResult(vm);
}
}
/VM
public class ExportEmployeeRecordVm
{
public string FileName { get; set; }
public string ContentType { get; set; }
public byte[] Content { get; set; }
}
/Azure function
public class ExportAgentEmployeeRecordFunction
{
private readonly IMediator _mediator;
private readonly IHttpFunctionExecutor _httpFunctionExecutor;
public ExportAgentEmployeeRecordFunction(IMediator mediator, IHttpFunctionExecutor httpFunctionExecutor)
{
_mediator = mediator;
_httpFunctionExecutor = httpFunctionExecutor;
}
[FunctionName("ExportAgentEmployeeRecordFunction")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = null)]
ExportEmployeeRecordQuery qry)
{
return await _httpFunctionExecutor.ExecuteAsync(async () =>
{
var res = await _mediator.Send(qry);
return new OkObjectResult(res);
});
}
}
I'm trying to return an Excel file but its always trying to save a JSON file instead of Excel file. Can you tell me what's wrong with this code? I thought the content type was wrong but I tried several options there and yet still returning a JSON.
You are using the wrong derived action result type
The OkObjectResult
An ObjectResult that when executed performs content negotiation, formats the entity body, and will produce a Status200OK response if negotiation and formatting succeed
Which mean it is wrapping the passed view model and returning that as JSON based on the request.
Given what you are trying to achieve you would need to use the FileContentResult Class,
Represents an ActionResult that when executed will write a binary file to the response.
and populate it using the members of the returned view model
For example
//...
[FunctionName("ExportAgentEmployeeRecordFunction")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = null)]
ExportEmployeeRecordQuery qry) {
return await _httpFunctionExecutor.ExecuteAsync(async () => {
ExportEmployeeRecordVm response = await _mediator.Send(qry);
FileContentResult result = new FileContentResult(response.Content, response.ContentType){
FileDownloadName = response.FileName
};
return result;
});
}
//...
You could use OfficeOpenXml library to export to an excel file.
sample code :
var data = YourDataSource ;
ExcelPackage excel = new ExcelPackage();
var workSheet = excel.Workbook.Worksheets.Add("Sheet1");
workSheet.Cells[1, 1].LoadFromCollection(data, true);
using (var memoryStream = new MemoryStream())
{
Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
Response.AddHeader("content-disposition", $"attachment; filename=myfile.xlsx");
excel.SaveAs(memoryStream);
memoryStream.WriteTo(Response.OutputStream);
Response.Flush();
Response.End();
}

Return image file from Asp Net Core WebApi [duplicate]

Problem
I want to return a file in my ASP.Net Web API Controller, but all my approaches return the HttpResponseMessage as JSON.
Code so far
public async Task<HttpResponseMessage> DownloadAsync(string id)
{
var response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StreamContent({{__insert_stream_here__}});
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
return response;
}
When I call this endpoint in my browser, the Web API returns the HttpResponseMessage as JSON with the HTTP Content Header set to application/json.
If this is ASP.net-Core then you are mixing web API versions. Have the action return a derived IActionResult because in your current code the framework is treating HttpResponseMessage as a model.
[Route("api/[controller]")]
public class DownloadController : Controller {
//GET api/download/12345abc
[HttpGet("{id}")]
public async Task<IActionResult> Download(string id) {
Stream stream = await {{__get_stream_based_on_id_here__}}
if(stream == null)
return NotFound(); // returns a NotFoundResult with Status404NotFound response.
return File(stream, "application/octet-stream", "{{filename.ext}}"); // returns a FileStreamResult
}
}
Note:
The framework will dispose of the stream used in this case when the response is completed. If a using statement is used, the stream will be disposed before the response has been sent and result in an exception or corrupt response.
You can return FileResult with this methods:
1: Return FileStreamResult
[HttpGet("get-file-stream/{id}"]
public async Task<FileStreamResult> DownloadAsync(string id)
{
var fileName="myfileName.txt";
var mimeType="application/....";
Stream stream = await GetFileStreamById(id);
return new FileStreamResult(stream, mimeType)
{
FileDownloadName = fileName
};
}
2: Return FileContentResult
[HttpGet("get-file-content/{id}"]
public async Task<FileContentResult> DownloadAsync(string id)
{
var fileName="myfileName.txt";
var mimeType="application/....";
byte[] fileBytes = await GetFileBytesById(id);
return new FileContentResult(fileBytes, mimeType)
{
FileDownloadName = fileName
};
}
Here is a simplistic example of streaming a file:
using System.IO;
using Microsoft.AspNetCore.Mvc;
[HttpGet("{id}")]
public async Task<FileStreamResult> Download(int id)
{
var path = "<Get the file path using the ID>";
var stream = File.OpenRead(path);
return new FileStreamResult(stream, "application/octet-stream");
}
Note:
Be sure to use FileStreamResult from Microsoft.AspNetCore.Mvc and not from System.Web.Mvc.
ASP.NET 5 WEB API & Angular 12
You can return a FileContentResult object (Blob) from the server. It'll not get automatically downloaded. You may create an anchor tag in your front-end app programmatically and set the href property to an object URL created from the Blob by the method below. Now clicking on the anchor will download the file. You can set a file name by setting the 'download' attribute to the anchor as well.
downloadFile(path: string): Observable<any> {
return this._httpClient.post(`${environment.ApiRoot}/accountVerification/downloadFile`, { path: path }, {
observe: 'response',
responseType: 'blob'
});
}
saveFile(path: string, fileName: string): void {
this._accountApprovalsService.downloadFile(path).pipe(
take(1)
).subscribe((resp) => {
let downloadLink = document.createElement('a');
downloadLink.href = window.URL.createObjectURL(resp.body);
downloadLink.setAttribute('download', fileName);
document.body.appendChild(downloadLink);
downloadLink.click();
downloadLink.remove();
});
}
Backend
[HttpPost]
[Authorize(Roles = "SystemAdmin, SystemUser")]
public async Task<IActionResult> DownloadFile(FilePath model)
{
if (ModelState.IsValid)
{
try
{
var fileName = System.IO.Path.GetFileName(model.Path);
var content = await System.IO.File.ReadAllBytesAsync(model.Path);
new FileExtensionContentTypeProvider()
.TryGetContentType(fileName, out string contentType);
return File(content, contentType, fileName);
}
catch
{
return BadRequest();
}
}
return BadRequest();
}
Following is the basic example of returning file (e.g Image file) in .NET Core Web API:
<img src="#Url.Action("RenderImage", new { id = id})" alt="No Image found" />
Below is the code for returning File from controller to view. Following is Action method which will return file:
[Route("api/[controller]")]
public class DownloadController : Controller
{
//GET api/download/123
[HttpGet]
public async Task<IActionResult> RenderImage(string userId)
{
//get Image file using _fileservice from db
var result = await _fileService.getFile(userId);
if (result.byteStream == null)
return NotFound();
return File(result.byteStream, result.ContentType, result.FileName);
}
}
Note:
Our file should be first converted into byte[] and then saved in database as varbinary(max) in order to retrieve.
FileStreamResult works for me. and File is not an IActionResult. I don't know how it can work.
add
builder.Services.AddSingleton();
in
Program.cs
[HttpGet("{fileId}")]
public ActionResult GetFile(string fileId)
{
string pathToFile = "test.rar";
if (!System.IO.File.Exists(pathToFile))
{
return NotFound();
}
if(!_fileExtensionContentTypeProvider.TryGetContentType(pathToFile,
out var ContentType))
{
ContentType = "application/octet-stream";
}
var byets=System.IO.File.ReadAllBytes(pathToFile);
return File(byets, ContentType, Path.GetFileName(pathToFile));
}
}

Filename specification not working?

I have a FileResult class inspired by a SO post that looks like this:
public class FileResult : IHttpActionResult
{
private readonly Stream _content;
private readonly string _contentType;
private readonly string _filename;
private readonly HttpStatusCode _status;
public FileResult(Stream content, string filename, string contentType = null, HttpStatusCode status = HttpStatusCode.OK)
{
_filename = filename;
_contentType = contentType;
_content = content;
_status = status;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
return Task.Run(() =>
{
var response = new HttpResponseMessage(_status)
{
Content = new StreamContent(_content)
};
var contentType = _contentType ?? MimeMapping.GetMimeMapping(_filename);
response.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = _filename,
Size = _content.Length
};
response.Content.Headers.ContentLength = _content.Length;
return response;
},
cancellationToken);
}
}
I wire this up in my web api controller like this:
[Route("~/campaigns/{campaignId}/creativetemplate")]
[HttpGet]
public async Task<IHttpActionResult> GetBulkEditSheetForCampaign(int campaignId)
{
var sheetStream = await _creativeBulkOperationService.GenerateBulkCreativeEditSpreadsheet(campaignId);
return new FileResult(sheetStream, $"CreativeEditSheet_{campaignId}.xslx");
}
When I call this with my RESTful client, the file is saved as creativetemplate. I would expect it to be saved as something resembling CreativeEditSheet_1234567890.xslx. Am I doing something wrong, or is this a client-side behavior?
It looks like the consensus is I did this correctly, but the client I am using is ignoring it... which is both good news (I didn't screw anything up) and bad news (now I have to find a better client).
Thanks to all who investigated.

Return file in ASP.Net Core Web API

Problem
I want to return a file in my ASP.Net Web API Controller, but all my approaches return the HttpResponseMessage as JSON.
Code so far
public async Task<HttpResponseMessage> DownloadAsync(string id)
{
var response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StreamContent({{__insert_stream_here__}});
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
return response;
}
When I call this endpoint in my browser, the Web API returns the HttpResponseMessage as JSON with the HTTP Content Header set to application/json.
If this is ASP.net-Core then you are mixing web API versions. Have the action return a derived IActionResult because in your current code the framework is treating HttpResponseMessage as a model.
[Route("api/[controller]")]
public class DownloadController : Controller {
//GET api/download/12345abc
[HttpGet("{id}")]
public async Task<IActionResult> Download(string id) {
Stream stream = await {{__get_stream_based_on_id_here__}}
if(stream == null)
return NotFound(); // returns a NotFoundResult with Status404NotFound response.
return File(stream, "application/octet-stream", "{{filename.ext}}"); // returns a FileStreamResult
}
}
Note:
The framework will dispose of the stream used in this case when the response is completed. If a using statement is used, the stream will be disposed before the response has been sent and result in an exception or corrupt response.
You can return FileResult with this methods:
1: Return FileStreamResult
[HttpGet("get-file-stream/{id}"]
public async Task<FileStreamResult> DownloadAsync(string id)
{
var fileName="myfileName.txt";
var mimeType="application/....";
Stream stream = await GetFileStreamById(id);
return new FileStreamResult(stream, mimeType)
{
FileDownloadName = fileName
};
}
2: Return FileContentResult
[HttpGet("get-file-content/{id}"]
public async Task<FileContentResult> DownloadAsync(string id)
{
var fileName="myfileName.txt";
var mimeType="application/....";
byte[] fileBytes = await GetFileBytesById(id);
return new FileContentResult(fileBytes, mimeType)
{
FileDownloadName = fileName
};
}
Here is a simplistic example of streaming a file:
using System.IO;
using Microsoft.AspNetCore.Mvc;
[HttpGet("{id}")]
public async Task<FileStreamResult> Download(int id)
{
var path = "<Get the file path using the ID>";
var stream = File.OpenRead(path);
return new FileStreamResult(stream, "application/octet-stream");
}
Note:
Be sure to use FileStreamResult from Microsoft.AspNetCore.Mvc and not from System.Web.Mvc.
ASP.NET 5 WEB API & Angular 12
You can return a FileContentResult object (Blob) from the server. It'll not get automatically downloaded. You may create an anchor tag in your front-end app programmatically and set the href property to an object URL created from the Blob by the method below. Now clicking on the anchor will download the file. You can set a file name by setting the 'download' attribute to the anchor as well.
downloadFile(path: string): Observable<any> {
return this._httpClient.post(`${environment.ApiRoot}/accountVerification/downloadFile`, { path: path }, {
observe: 'response',
responseType: 'blob'
});
}
saveFile(path: string, fileName: string): void {
this._accountApprovalsService.downloadFile(path).pipe(
take(1)
).subscribe((resp) => {
let downloadLink = document.createElement('a');
downloadLink.href = window.URL.createObjectURL(resp.body);
downloadLink.setAttribute('download', fileName);
document.body.appendChild(downloadLink);
downloadLink.click();
downloadLink.remove();
});
}
Backend
[HttpPost]
[Authorize(Roles = "SystemAdmin, SystemUser")]
public async Task<IActionResult> DownloadFile(FilePath model)
{
if (ModelState.IsValid)
{
try
{
var fileName = System.IO.Path.GetFileName(model.Path);
var content = await System.IO.File.ReadAllBytesAsync(model.Path);
new FileExtensionContentTypeProvider()
.TryGetContentType(fileName, out string contentType);
return File(content, contentType, fileName);
}
catch
{
return BadRequest();
}
}
return BadRequest();
}
Following is the basic example of returning file (e.g Image file) in .NET Core Web API:
<img src="#Url.Action("RenderImage", new { id = id})" alt="No Image found" />
Below is the code for returning File from controller to view. Following is Action method which will return file:
[Route("api/[controller]")]
public class DownloadController : Controller
{
//GET api/download/123
[HttpGet]
public async Task<IActionResult> RenderImage(string userId)
{
//get Image file using _fileservice from db
var result = await _fileService.getFile(userId);
if (result.byteStream == null)
return NotFound();
return File(result.byteStream, result.ContentType, result.FileName);
}
}
Note:
Our file should be first converted into byte[] and then saved in database as varbinary(max) in order to retrieve.
FileStreamResult works for me. and File is not an IActionResult. I don't know how it can work.
add
builder.Services.AddSingleton();
in
Program.cs
[HttpGet("{fileId}")]
public ActionResult GetFile(string fileId)
{
string pathToFile = "test.rar";
if (!System.IO.File.Exists(pathToFile))
{
return NotFound();
}
if(!_fileExtensionContentTypeProvider.TryGetContentType(pathToFile,
out var ContentType))
{
ContentType = "application/octet-stream";
}
var byets=System.IO.File.ReadAllBytes(pathToFile);
return File(byets, ContentType, Path.GetFileName(pathToFile));
}
}

Categories

Resources