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.
Related
I created a .NET 5 REST Api. I can easily upload files from swagger. That is working fine. When debugging, I can see that the byte array is not empty. Here is the Controller method:
[Route("api/[controller]")]
[ApiController]
public class ImageController : ControllerBase
{
// POST api/<ImageController>
[HttpPost]
public void Post([FromForm] UserModel info)
{
var memoryStream = new MemoryStream();
info.Avatar.CopyTo(memoryStream);
var bytes = memoryStream.ToArray();
}
}
This is the UserModel:
public class UserModel
{
[FromForm(Name = "avatar")]
public IFormFile Avatar { get; set; }
[FromForm(Name = "name")]
public string Name { get; set; }
}
I also tried to upload a file programmatically. This is not entirely working. When putting breakpoints in the controller method, I see that the byte array is empty. So the call itself is working but the data is not entering.
Here is the source code of the .NET 5 Console application to upload files.
As explained, this does something useful as it really calls the REST API which I can see by putting breakpoints in the controller method. However, my controller method does not get any data. The byte array is empty.
private static async Task TryUpload()
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:5000");
string filePath = "C:\\Users\\daan1982\\Pictures\\RiderStart.png";
var fileStream = File.Create(filePath);
using (var content =
new MultipartFormDataContent("Upload----" + DateTime.Now.ToString(CultureInfo.InvariantCulture)))
{
content.Add(new StreamContent(fileStream), "avatar", "RiderStart.png");
var result = await client.PostAsync("/api/Image", content);
var request = result.RequestMessage;
}
}
}
static async Task Main(string[] args)
{
await TryUpload();
Console.WriteLine("Hello World!");
}
As I named the content "avatar" in the upload and also in the request model, this should work fine. However, it does work but not fine as the byte array is always empty.
What am I doing wrong? And how can I fix this?
File.Create "creates or overwrites a file in the specified path."
You probably want File.OpenRead.
That's how it worked for me.
static async Task Main(string[] args)
{
await TryUpload();
}
private const string Boundary = "EAD567A8E8524B2FAC2E0628ABB6DF6E";
private static readonly HttpClient HttpClient = new()
{
BaseAddress = new Uri("https://localhost:5001/")
};
private static async Task TryUpload()
{
var requestContent = new MultipartFormDataContent(Boundary);
requestContent.Headers.Remove("Content-Type");
requestContent.Headers.TryAddWithoutValidation("Content-Type", $"multipart/form-data; boundary={Boundary}");
var fileContent = await File.ReadAllBytesAsync(#"<path to file\Unbenannt.PNG");
var byteArrayContent = new ByteArrayContent(fileContent);
byteArrayContent.Headers.ContentType = MediaTypeHeaderValue.Parse("image/png");
requestContent.Add(byteArrayContent, "avatar", "Unbenannt.PNG");
var postResponse = await HttpClient.PostAsync("/api/Image", requestContent);
}
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);
}
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));
}
}
This is client code:
public class Class1
{
static void Main()
{
string data = "Some big string...";
Work(data);
Console.WriteLine("Press any key for exit...");
Console.ReadKey();
}
async static void Work(string data)
{
var guid = Guid.NewGuid();
Uri uri = new Uri("http://localhost:61698/Home/Task/" + guid.ToString());
using (Stream s = StringToStream(data))
{
var report = await PostStream(uri, guid, s);
}
}
public static Stream StringToStream(string s)
{
MemoryStream stream = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
writer.Write(s);
writer.Flush();
stream.Position = 0;
return stream;
}
async static Task<byte[]> PostStream(Uri uri, Guid guid, Stream stream)
{
try
{
using (StreamReader sr = new StreamReader(stream))
{
string text = await sr.ReadToEndAsync();
var host = Dns.GetHostEntry(Dns.GetHostName());
IPAddress ip = null;
foreach (var item in host.AddressList)
{
if (item.AddressFamily == AddressFamily.InterNetwork)
{
ip = item;
}
}
var client = new WebClient();
var values = new NameValueCollection();
values.Add("Guid", guid.ToString());
values.Add("IP", ip.ToString());
values.Add("Data", text);
var bytes = await client.UploadValuesTaskAsync(uri, values);
return bytes;
}
}
catch(Exception err)
{
Console.Error.WriteLine(err.Message);
return null;
}
}
}
This is controller on the server side:
public class HomeController : Controller
{
[HttpGet]
public IActionResult Task(Guid id)
{
return Ok();
}
[HttpPost]
public IActionResult Task(NameValueCollection data)
{
if (ModelState.IsValid)
{
var count = data.Count; // 0
return Ok();
}
else
{
return BadRequest();
}
}
}
In debug mode I see that NameValueCollection on the server side is empty. Why does it happen & how can I fix it?
UploadValuesTaskAsync() sends request in form-urlencoded format:
Content-Type: application/x-www-form-urlencoded
Guid=660d9902-2293-43eb-906f-374adf77a9d6&IP=192.168.100.3&Data=Some+big+string...
Form data is bound to action parameters matched by name. So if your controller has following signature:
[HttpPost]
public IActionResult Task(Guid guid, string ip, string data)
guid, ip and data will be bound correctly to values from request.
If you want to change this behavior and deserialize request data to NameValueCollection, you could try marking action parameter with FromBody attribute:
[HttpPost]
public IActionResult Task([FromBody] NameValueCollection data)
Ufortunatelly, this will give you 415 Unsupported Media Type response. This happens because ASP.NET Core supports only JSON and XML media type formatters out of the box.
You have to add custom media type formatter to make this work. Quick search brought this project on Github. I haven't tried it but at least you could get the idea how to implement it.
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));
}
}