How to do Multipart-form-data file upload in asp.net core web api? Is it possible to POST both JSON and the image at the same time in a single POST?
Update- .net core 2.0 +
With .net core you can leverage the new IFormFile interface to upload both the image and properties in the same post. For example:
[HttpPost("content/upload-image")]
public async Task<IActionResult> UploadImage(MyFile upload)
The MyFile class can look like:
public class MyFile
{
public string userId { get; set; }
public IFormFile File { get; set; }
// Other properties
}
You can access the properties and the file as follows:
var file = upload.File // This is the IFormFile file
var param = upload.userId // param
To persist/save the file to disk you can do the following:
using (var stream = new FileStream(path, FileMode.Create))
{
await file.File.CopyToAsync(stream);
}
.NET Framework
Yes it is. Depending on the client Framework you're using, you can configure your Web API for Content Type-Multipart, then do something like:
[HttpPost]
[Route("content/upload-image")]
public async Task<HttpResponseMessage> Post()
{
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
// enter code here
}
Define and setup the directory where your image will be saved.
var root = HttpContext.Current.Server.MapPath("~/Content/Images/");
if (!Directory.Exists(root))
{
Directory.CreateDirectory(root);
}
Setup the StreamProvider and attempt to get the model data, which is the JSON you mentioned.
var streamProvider = new MultipartFormDataStreamProvider(root);
var result =
await Request.Content.ReadAsMultipartAsync(streamProvider);
if (result.FormData["model"] == null)
{
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
Now access the files in the request.
try
{
// Deserialize model data to your own DTO
var model = result.FormData["model"];
var formDto = JsonConvert
.DeserializeObject<MyDto>(model, new IsoDateTimeConverter());
var files = result.FileData.ToList();
if (files != null)
{
foreach (var file in files)
{
// Do anything with the file(s)
}
}
}
I created the Wep API in ASP.Net core to return the PDF. Here is my code:
public HttpResponseMessage Get(int id)
{
var response = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
var stream = new System.IO.FileStream(#"C:\Users\shoba_eswar\Documents\REquest.pdf", System.IO.FileMode.Open);
response.Content = new StreamContent(stream);
response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
response.Content.Headers.ContentDisposition.FileName = "NewTab";
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/pdf");
return response;
}
But it returns only the JSON response:
{
"version":{
"major":1,
"minor":1,
"build":-1,
"revision":-1,
"majorRevision":-1,
"minorRevision":-1
},
"content":{
"headers":[
{
"key":"Content-Disposition",
"value":[
"attachment; filename=NewTab"
]
},
{
"key":"Content-Type",
"value":[
"application/pdf"
]
}
]
},
"statusCode":200,
"reasonPhrase":"OK",
"headers":[
],
"requestMessage":null,
"isSuccessStatusCode":true
}
Am I doing anything wrong here?
As explained in ASP.NET Core HTTPRequestMessage returns strange JSON message, ASP.NET Core does not support returning an HttpResponseMessage (what package did you install to get access to that type?).
Because of this, the serializer is simply writing all public properties of the HttpResponseMessage to the output, as it would with any other unsupported response type.
To support custom responses, you must return an IActionResult-implementing type. There's plenty of those. In your case, I'd look into the FileStreamResult:
public IActionResult Get(int id)
{
var stream = new FileStream(#"path\to\file", FileMode.Open);
return new FileStreamResult(stream, "application/pdf");
}
Or simply use a PhysicalFileResult, where the stream is handled for you:
public IActionResult Get(int id)
{
return new PhysicalFileResult(#"path\to\file", "application/pdf");
}
Of course all of this can be simplified using helper methods, such as Controller.File():
public IActionResult Get(int id)
{
var stream = new FileStream(#"path\to\file", FileMode.Open);
return File(stream, "application/pdf", "FileDownloadName.ext");
}
This simply abstracts the creation of a FileContentResult or FileStreamResult (for this overload, the latter).
Or if you're converting an older MVC or Web API application and don't want to convert all your code at once, add a reference to WebApiCompatShim (NuGet) and wrap your current code in a ResponseMessageResult:
public IActionResult Get(int id)
{
var response = new HttpResponseMessage(HttpStatusCode.OK);
var stream = ...
response.Content...
return new ResponseMessageResult(response);
}
If you don't want to use return File(fileName, contentType, fileDownloadName), then the FileStreamResult doesn't support setting the content-disposition header from the constructor or through properties.
In that case you'll have to add that response header to the response yourself before returning the file result:
var contentDisposition = new ContentDispositionHeaderValue("attachment");
contentDisposition.SetHttpFileName("foo.txt");
Response.Headers[HeaderNames.ContentDisposition] = contentDisposition.ToString();
I couldn't comment the answer by CodeCaster since my reputation isn't high enough.
When trying
public IActionResult Get(int id)
{
using (var stream = new FileStream(#"path\to\file", FileMode.Open))
{
return File(stream, "application/pdf", "FileDownloadName.ext");
}
}
we got a
ObjectDisposedException: Cannot access a disposed object. Object name:
'Cannot access a closed file.'. System.IO.FileStream.BeginRead(byte[]
array, int offset, int numBytes, AsyncCallback callback, object state)
We removed the using
[HttpGet]
[Route("getImageFile")]
public IActionResult GetWorkbook()
{
var stream = new FileStream(#"pathToFile", FileMode.Open);
return File(stream, "image/png", "image.png");
}
And that worked. This is ASP.NET Core 2.1 running in IIS Express.
I don't have enough reputation to post this as a comment, so posting as an answer. The first 3 solutions from #CodeCaster and the solution from #BernhardMaertl are correct.
However, for someone who may not work with files often (like me), please note that if the process running this code (e.g. the API) only has read permissions to the file, you will need to specify that as the third parameter when creating your FileStream, otherwise the default behavior is to open the file for read/write and you will get an exception since you do not have write permissions.
The 3rd solution from #CodeCaster would then look like this:
public IActionResult Get(int id)
{
var stream = new FileStream(#"path\to\file", FileMode.Open, FileAccess.Read);
return File(stream, "application/pdf", "FileDownloadName.ext");
}
I am currently uploading a file via the kendo fileuploader to an api controller using ASP.NET core RC-1. I am receiving a periodic error of "object reference not set to instance of object" when attempting to read the stream following opening the stream with IFormFile.OpenReadStream().
My controller is:
[HttpPost]
[Route("api/{domain}/[controller]")]
public async Task<IActionResult> Post([FromRoute]string domain, [FromForm]IFormFile file, [FromForm]WebDocument document)
{
if (ModelState.IsValid)
{
if (file.Length > 0)
{
var userName =
Request.HttpContext.User.Claims
.FirstOrDefault(c => c.Type == ClaimTypesEx.FullName)?
.Value;
var uploadedFileName =
ContentDispositionHeaderValue.Parse(file.ContentDisposition).FileName.Trim('"');
document.Domain = domain;
document.MimeType = file.ContentType;
document.SizeInBytes = file.Length;
document.ChangedBy = userName;
document.FileName = (string.IsNullOrEmpty(document.FileName)) ? uploadedFileName : document.FileName;
try
{
document = await CommandStack.For<WebDocument>()
.AddOrUpdateAsync(document, file.OpenReadStream()).ConfigureAwait(false);
}
catch (Exception e)
{
return new HttpStatusCodeResult(500);
}
return Ok(document);
}
}
return new BadRequestResult();
}
And the error is being thrown when I actually try to read the stream when it is going into blob storage:
public async Task<Uri> CreateOrUpdateBlobAsync(string containerName, string fileName, string mimeType,
Stream fileStream)
{
var container = Client.GetContainerReference(containerName);
var blob = container.GetBlockBlobReference(fileName);
//Error HERE
await blob.UploadFromStreamAsync(fileStream);
blob.Properties.ContentType = mimeType;
await blob.SetPropertiesAsync();
return blob.Uri;
}
What I am having trouble with is this is sporadic and there seems to be no defined pattern of which files are accepted and which ones generate the error. At first I thought it might be a size issue but that is not the case as I have several larger files uploaded successfully and then one small file will throw the error. Images seem to work fine and it is hit or miss on other file types with no rhyme or reason that I can figure out.
Here is the controller.
I need to upload a image to AWS S3 but I'm get a error . I'm using the MVC project for asp application.
[HttpPost, ValidateInput(false)]
[ValidateAntiForgeryToken]
public ActionResult Nueva(Historia historia, HttpPostedFileBase HeroImagen)
{
try
{
if (ModelState.IsValid)
{
IAmazonS3 client;
using (client = Amazon.AWSClientFactory.CreateAmazonS3Client(_awsAccessKey, _awsSecretKey))
{
var request = new PutObjectRequest()
{
BucketName = _bucketName,
CannedACL = S3CannedACL.PublicRead,
Key = string.Format("UPLOADS/{0}", HeroImagen.FileName),
InputStream = HeroImagen.InputStream
};
client.PutObject(request);
}
historia.HeroImagen = HeroImagen.FileName;
db.Historias.Add(historia);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.AutorID = new SelectList(db.Autores, "AutorID", "AutorNombre", historia.AutorID);
return View(historia);
}
catch (Exception)
{
return View();
}
}
But, when I submit the form get an error.
.
For me it look like your AWS related code is not even running.
You get an error based on the line "Viewbag.AutorID = ...", which means, the redirect command above it was never running. For me it looks like your ModelState is invalid.
Note: Please copy your code and exception to your question as text, that would make your question searchable.
In a web api method I am generating a file and then streaming it to the response like so
public async Task<HttpResponseMessage> GetFile() {
FileInfo file = generateFile();
var msg = Request.CreateResponse(HttpStatusCode.OK);
msg.Content = new StreamContent(file.OpenRead());
msg.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
msg.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") {FileName = file.Name};
return msg;
}
because this a generated file I want to delete it after the response has finished streaming but I can't seem to find a hook in the pipeline for this.
I suppose that I can put a reference to the file in a static and set up a custom MessageHandler that pulls values out of this same static variable and deletes. However, this seems like it can't possibly be right both because of the use of a static (when this should all be per-request) and because I'd have to register a separate route.
I've seen this question but it seems to not really have much of a useful response.
Nice scenario!...the problem with using message handlers is that response writing happens at the host layers and below message handlers layer, so they are not ideal...
Following is an example of how you could do it:
msg.Content = new CustomStreamContent(generatedFilePath);
public class CustomStreamContent : StreamContent
{
string filePath;
public CustomStreamContent(string filePath)
: this(File.OpenRead(filePath))
{
this.filePath = filePath;
}
private CustomStreamContent(Stream fileStream)
: base(content: fileStream)
{
}
protected override void Dispose(bool disposing)
{
//close the file stream
base.Dispose(disposing);
try
{
File.Delete(this.filePath);
}
catch (Exception ex)
{
//log this exception somewhere so that you know something bad happened
}
}
}
By the way, are you generating this file because you are converting some data into PDF. If yes, then I think you could use PushStreamContent for this purpose by directly writing the converted data into the response stream. This way you need not generate a file first and then worry about deleting it later.
We performed same action in WebAPI. I needed to delete file just after it downloaded form server.
We can create custom response message class. It takes file path as parameter and delete it once its transmitted.
public class FileResponseMessage : HttpResponseMessage
{
private readonly string _filePath;
public FileHttpResponseMessage(string filePath)
{
this._filePath= filePath;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
Content.Dispose();
File.Delete(_filePath);
}
}
Use this class as below code and it will delete your file once it will be written on response stream.
var response = new FileResponseMessage(filePath);
response.StatusCode = HttpStatusCode.OK;
response.Content = new StreamContent(new FileStream(filePath, FileMode.Open, FileAccess.Read));
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = "MyReport.pdf"
};
return response;