Passing file and object to body in Post - c#

I get blob urls and able to create a file for each blob url and send them to backend, however, I also need to pass a bunch of a different object's properties. (these properties are not related to file at all)
The problem is I cannot send them together to backend. Of course I tried to send only one object and passed my form data (file) as an object property but it didn't work. I also tried content-type: multipart/form-data.
Please do not worry about syntax errors.
export const sendObjectAndFile = (myObj, file) => {
return function(dispatch) {
let form = new FormData();
form.append('pdf file', file)
return fetch(URL, {
method: "POST",
credentials: 'include',
body: myObj /* If I pass only form or only myObj that works, however, how can I send them together */
}).then(response =>
if (response.success) {
} else {
}
});
}
To give an idea about how I handle blob urls in backed (I am assuming I passed only files)
var myFile = Request.Files;

You can try formData.append(${property name}, object), then pass the whole form data.
Eg.
let formData = new FormData();
formData.append("myFile", $("#file")[0].files[0]);
formData.append("title", $("#title").val();
Then in C# controller
public void Post(HttpPostedFileBase myFile, string title){
// implementation here
}

Related

415 unsupported media type error while trying to upload file from react to web api

I am new to react and web api and I am getting this unsupported media type error while trying to upload my file into my web api function
Here is my react js code:
onFileUpload = () => {
// Create an object of formData
const formData = new FormData();
console.log("FormData");
// Update the formData object
formData.append('myFile', this.state.selectedFile, this.state.selectedFile.name);
console.log(this.state.selectedFile);
// Request made to the backend api
// Send formData object
//axios.post("api/uploadfile", formData);
axiosAPI.post('api/observation/Uploadfile', formData).then(response => {});
};
The corresponding web api code is this:
[HttpPost]
[Route("Uploadfile")]
public IHttpActionResult Uploadfile(object formData)
{
try
{
return Ok(formData);
}
catch (Exception ex)
{
return Content(HttpStatusCode.NoContent, "Something went wrong");
}
}
I am trying to upload the file and get it to my backend however I am getting this error. What does this error mean and how do I resolve this?
I had the same issue, I'm using .NET 6 for the API and React with Axios for the frontend.
To be able to get the file in the controller, I had to use the "FromForm" attribute, otherwise, I was getting the 415 error when I tried to do the Post Request.
[HttpPost]
public async Task<IActionResult> Create([FromForm] ExampleDto dto)
{
/// ...
}
To send the data from the React Application using Axios, I just created the FormData object just like you did, and made a post request without any special headers o configuration.
Hope this can help you solve your issue.
Whenever you are uploading a file, make sure to add appropriate headers to the request.
axiosAPI.post('api/observation/Uploadfile', formData, {
headers: {
'Content-Type': 'multipart/form-data' // <- HERE
}
}).then(response => {});
Sometime the issue is on formData, you need to append file obj on formData then send to api.
const formData = new FormData();
var file = fileList[0].originFileObj;
formData.append("excelFormFile", file); //Append file obj to formData
//excelFormFile name similar to .NET Core Api Like
c# Code : ' public async Task PostProductImport(IFormFile excelFormFile) '

Multiple form-data file upload parameters

I'd like to use the ng2-file-upload component on the client side and everything works so far but now I have to pass an additional parameter with every file that contains an identifier the file is attached to.
I try to set the additionalParameter property of the FileUploader object in TypeScript:
this.uploader.options.additionalParameter = {"issueId": result.data.id};
On the server I have the following method that is working except I don't get the additional parameter (issueId) set above. (.NET Core 2.0)
public RequestResultModel File(IFormFile file);
The request payload contains the parameter but in a new form-data:
------WebKitFormBoundaryegtYAcYfO3gKdk9Z
Content-Disposition: form-data; name="file"; filename="81980085.pdf"
Content-Type: application/pdf
------WebKitFormBoundaryegtYAcYfO3gKdk9Z
Content-Disposition: form-data; name="issueId"
19
------WebKitFormBoundaryegtYAcYfO3gKdk9Z--
How can I modify the controller method in order to read the issueId parameter as well? In a previous project I used a second parameter public async Task<ApiResultBase> Upload(IFormFile file, string projectid) and it worked but now I would like to use this client side component because I don't want to suck with drag and drop and I'm lazy.
I have tried to change the component's POST url after initialize (this.uploader.options.url = "/api/Issue/File/"+result.data.id;) but it tries to POST to the original address.
You are on track. I have a slightly different approach you can try out.On the client, try something like:
this.uploader = new FileUploader({
url: url,//The enpoint you are consuming
additionalParameter: {
issueId: result.data.id //your parameter-remove quotes
},
headers: [{ name: 'Accept', value: 'application/json' }],//your custom header
//autoUpload: true, //configure autoUpload
});
The library also has onErrorItem and onSuccessItem callbacks that you can leverage like below:
this.uploader.onErrorItem = (item, response, status, headers) => this.onErrorItem(item, response, status, headers);
this.uploader.onSuccessItem = (item, response, status, headers) => this.onSuccessItem(item, response, status, headers);
Then(Optional) - Callbacks:
onSuccessItem(item: FileItem, response: string, status: number, headers:ParsedResponseHeaders): any {
//this gets triggered only once when first file is uploaded
}
onErrorItem(item: FileItem, response: string, status: number, headers:
ParsedResponseHeaders): any {
let error = JSON.parse(response); //error server response
}
On the API side you can restructure like below - Change it to your own signature.
public async Task<IActionResult> UploadImages(MyFile upload)
Then the MyFile model can be something like:
public class MyFile
{
public string issueId { get; set; }
public IFormFile File { get; set; }
}
To get the param and the file:
var file = upload.File //This is the IFormFile file
var param = upload.issueId //param
To save the file to disk:
using (var stream = new FileStream(path, FileMode.Create))
{
await file.File.CopyToAsync(stream);
}

How to send files along with the viewModel using Web API or how to save using temporary data

I've read many stackoverflow posts with the similar problems as well as several blogs but I am still uncertain as how to solve my problem :(
I have angularJS directive that allows to upload files to the server. The code is like this:
[HttpPost]
[Route("UploadFile")]
public async Task<HttpResponseMessage> UploadFile()
{
// Check if the request contains multipart/form-data.
if (Request.Content.IsMimeMultipartContent("form-data"))
{
try
{
var resultOut = new List<FileUploadResult>();
var streamProvider = new MultipartMemoryStreamProvider();
streamProvider = await Request.Content.ReadAsMultipartAsync(streamProvider);
foreach (
var item in
streamProvider.Contents.Where(c => !string.IsNullOrEmpty(c.Headers.ContentDisposition.FileName))
)
{
FileUploadResult file = new FileUploadResult()
{
FileName = item.Headers.ContentDisposition.FileName,
// Content = fileBytes, // No need to pass the info back as we're not going to read it save it yet
Key = Guid.NewGuid().ToString(),
Type = item.Headers.ContentDisposition.DispositionType
};
resultOut.Add(file);
//using (Stream stFileSource = new MemoryStream(await item.ReadAsByteArrayAsync())) {
// byte[] fileBytes;
// fileBytes = new Byte[stFileSource.Length];
// stFileSource.Read(fileBytes, 0, Convert.ToInt32(stFileSource.Length));
// FileUploadResult file = new FileUploadResult()
// {
// FileName = item.Headers.ContentDisposition.FileName,
// // Content = fileBytes, // No need to pass the info back as we're not going to read it save it yet
// Key = Guid.NewGuid().ToString(),
// Type = item.Headers.ContentDisposition.DispositionType
// };
// resultOut.Add(file);
//}
}
return Request.CreateResponse(HttpStatusCode.OK, resultOut.ToArray());
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.ToString());
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
else
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
Also directive saves the Files array into a property. My user form allows to remove some files / add more files and then I want to save the information from the form (somewhat complex view model) along with the files. I was unable to figure that problem so far. One possibility I see here is to save the files in the UploadFile method using Repository into a database. However, I would prefer to save that into some temporary table instead (e.g. #FileInfo table) and not the actual table. Or perhaps there is a way to save files (with its binary content) into some memory object so I will be able to get that content back when I am ready to save my model's data? Can you either show implementation of the temporary repository storage or give some other ideas for my dilemma?
Firstly, Your directive need to create a post request with 'multipart/form-data'.
Check this link for reference.
However, we use angular file upload to do this.
angular
.module('app', ['angularFileUpload'])
.controller('AppController', function($scope, FileUploader) {
$scope.uploader = new FileUploader(
{
url: 'Your/upload/url',
headers: {
'autorization': 'Bearer token if you need it'
},
onProgressItem: function () {
...
},
onSuccessItem: function (opt, data) {
...
},
onErrorItem: function (opt) {
...
}
});
//you may want to wrap the following in an event
var uploadItem = $scope.uploader.queue[uploader.queue.length - 1];
uploadItem.formData.push({
someData: "someData",
moreData: "moreData"
});
uploadItem.upload();
uploadItem.formData = [];
});
Then in your controller, you can do the following to retrieve what you need:
//your request
var request = HttpContext.Current.Request;
//your fields
var someData = request.Form["someData"];
var moreData = request.Form["moreData"];
//your file
var file = request.Files["file"];
Looks like a job for TempData:
TempData in ASP.NET MVC is basically a dictionary object derived from
TempDataDictionary. TempData stays for a subsequent HTTP Request as
opposed to other options (ViewBag and ViewData) those stay only for
current request. So, TempdData can be used to maintain data between
controller actions as well as redirects.
example:
//Controller Action 1 (TemporaryEmployee)
public ActionResult TemporaryEmployee()
{
Employee employee = new Employee
{
EmpID = "121",
EmpFirstName = "Imran",
EmpLastName = "Ghani"
};
TempData["Employee"] = employee;
return RedirectToAction("PermanentEmployee");
}
//Controller Action 2(PermanentEmployee)
public ActionResult PermanentEmployee()
{
Employee employee = TempData["Employee"] as Employee;
return View(employee);
}

unable to configure Web API for content type multipart

I am working on Web APIs - Web API 2. My basic need is to create an API to update the profile of the user. In this, the ios and android will send me the request in multipart/form-data. They will send me a few parameters with an image. But whenever I try to create the API, my model comes to be null every time.
I have also added this line in WebApiConfig:
config.Formatters.JsonFormatter.SupportedMediaTypes
.Add(new MediaTypeHeaderValue("multipart/form-data"));
This is my class:
public class UpdateProfileModel
{
public HttpPostedFileBase ProfileImage { get; set; }
public string Name { get; set; }
}
This is my controller:
[Route("api/Account/UpdateProfile")]
[HttpPost]
public HttpResponseMessage UpdateProfile(UpdateProfileModel model)
{
}
I am even not getting parameter values in my Model. Am I doing something wrong?
None of the answers related to this were helpful for me. It's about 3rd day and I have tried almost everything and every method. but I am unable to achieve it.
Although I can use this but this as shown below but this doesn't seem to be a good approach. so I am avoiding it.
var httpRequest = HttpContext.Current.Request;
if (httpRequest.Form["ParameterName"] != null)
{
var parameterName = httpRequest.Form["ParameterName"];
}
and for files I can do this:
if (httpRequest.Files.Count > 0)
{
//I can access my files here and save them
}
Please help if you have any good approach for it Or Please explain to me why I am unable to get these values in the Model.
Thanks a lot in Advance
The answer provided by JPgrassi is what you would be doing to have MultiPart data. I think there are few more things that needs to be added, so I thought of writing my own answer.
MultiPart form data, as the name suggest, is not single type of data, but specifies that the form will be sent as a MultiPart MIME message, so you cannot have predefined formatter to read all the contents. You need to use ReadAsync function to read byte stream and get your different types of data, identify them and de-serialize them.
There are two ways to read the contents. First one is to read and keep everything in memory and the second way is to use a provider that will stream all the file contents into some randomly name files(with GUID) and providing handle in form of local path to access file (The example provided by jpgrassi is doing the second).
First Method: Keeping everything in-memory
//Async because this is asynchronous process and would read stream data in a buffer.
//If you don't make this async, you would be only reading a few KBs (buffer size)
//and you wont be able to know why it is not working
public async Task<HttpResponseMessage> Post()
{
if (!request.Content.IsMimeMultipartContent()) return null;
Dictionary<string, object> extractedMediaContents = new Dictionary<string, object>();
//Here I am going with assumption that I am sending data in two parts,
//JSON object, which will come to me as string and a file. You need to customize this in the way you want it to.
extractedMediaContents.Add(BASE64_FILE_CONTENTS, null);
extractedMediaContents.Add(SERIALIZED_JSON_CONTENTS, null);
request.Content.ReadAsMultipartAsync()
.ContinueWith(multiPart =>
{
if (multiPart.IsFaulted || multiPart.IsCanceled)
{
Request.CreateErrorResponse(HttpStatusCode.InternalServerError, multiPart.Exception);
}
foreach (var part in multiPart.Result.Contents)
{
using (var stream = part.ReadAsStreamAsync())
{
stream.Wait();
Stream requestStream = stream.Result;
using (var memoryStream = new MemoryStream())
{
requestStream.CopyTo(memoryStream);
//filename attribute is identifier for file vs other contents.
if (part.Headers.ToString().IndexOf("filename") > -1)
{
extractedMediaContents[BASE64_FILE_CONTENTS] = memoryStream.ToArray();
}
else
{
string jsonString = System.Text.Encoding.ASCII.GetString(memoryStream.ToArray());
//If you need just string, this is enough, otherwise you need to de-serialize based on the content type.
//Each content is identified by name in content headers.
extractedMediaContents[SERIALIZED_JSON_CONTENTS] = jsonString;
}
}
}
}
}).Wait();
//extractedMediaContents; This now has the contents of Request in-memory.
}
Second Method: Using a provider (as given by jpgrassi)
Point to note, this is only filename. If you want process file or store at different location, you need to stream read the file again.
public async Task<HttpResponseMessage> Post()
{
HttpResponseMessage response;
//Check if request is MultiPart
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
//This specifies local path on server where file will be created
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
//This write the file in your App_Data with a random name
await Request.Content.ReadAsMultipartAsync(provider);
foreach (MultipartFileData file in provider.FileData)
{
//Here you can get the full file path on the server
//and other data regarding the file
//Point to note, this is only filename. If you want to keep / process file, you need to stream read the file again.
tempFileName = file.LocalFileName;
}
// You values are inside FormData. You can access them in this way
foreach (var key in provider.FormData.AllKeys)
{
foreach (var val in provider.FormData.GetValues(key))
{
Trace.WriteLine(string.Format("{0}: {1}", key, val));
}
}
//Or directly (not safe)
string name = provider.FormData.GetValues("name").FirstOrDefault();
response = Request.CreateResponse(HttpStatusCode.Ok);
return response;
}
By default there is not a media type formatter built into the api that can handle multipart/form-data and perform model binding. The built in media type formatters are :
JsonMediaTypeFormatter: application/json, text/json
XmlMediaTypeFormatter: application/xml, text/xml
FormUrlEncodedMediaTypeFormatter: application/x-www-form-urlencoded
JQueryMvcFormUrlEncodedFormatter: application/x-www-form-urlencoded
This is the reason why most answers involve taking over responsibility to read the data directly from the request inside the controller. However, the Web API 2 formatter collection is meant to be a starting point for developers and not meant to be the solution for all implementations. There are other solutions that have been created to create a MediaFormatter that will handle multipart form data. Once a MediaTypeFormatter class has been created it can be re-used across multiple implementations of Web API.
How create a MultipartFormFormatter for ASP.NET 4.5 Web API
You can download and build the full implementation of the web api 2 source code and see that the default implementations of media formatters do not natively process multi part data.
https://aspnetwebstack.codeplex.com/
You can't have parameters like that in your controller because there's no built-in media type formatter that handles Multipart/Formdata. Unless you create your own formatter, you can access the file and optional fields accessing via a MultipartFormDataStreamProvider :
Post Method
public async Task<HttpResponseMessage> Post()
{
HttpResponseMessage response;
//Check if request is MultiPart
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
//This write the file in your App_Data with a random name
await Request.Content.ReadAsMultipartAsync(provider);
foreach (MultipartFileData file in provider.FileData)
{
//Here you can get the full file path on the server
//and other data regarding the file
tempFileName = file.LocalFileName;
}
// You values are inside FormData. You can access them in this way
foreach (var key in provider.FormData.AllKeys)
{
foreach (var val in provider.FormData.GetValues(key))
{
Trace.WriteLine(string.Format("{0}: {1}", key, val));
}
}
//Or directly (not safe)
string name = provider.FormData.GetValues("name").FirstOrDefault();
response = Request.CreateResponse(HttpStatusCode.Ok);
return response;
}
Here's a more detailed list of examples:
Sending HTML Form Data in ASP.NET Web API: File Upload and Multipart MIME
Not so sure this would be helpful in your case , have a look
mvc upload file with model - second parameter posted file is null
and
ASP.Net MVC - Read File from HttpPostedFileBase without save
So, what worked for me is -
[Route("api/Account/UpdateProfile")]
[HttpPost]
public Task<HttpResponseMessage> UpdateProfile(/* UpdateProfileModel model */)
{
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
await Request.Content.ReadAsMultipartAsync(provider);
foreach (MultipartFileData file in provider.FileData)
{
}
}
Also -
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data"));
isn't required.
I guess the multipart/form-data is internally handled somewhere after the form is submitted.
Very clearly described here -
http://www.asp.net/web-api/overview/advanced/sending-html-form-data-part-2

Can I upload a file and a model to an MVC 4 action at the same time from a Winforms app?

I am required to integrate a signature pad into an intranet (MVC4) application allowing people to apply electronic signatures to system generated documents. Unfortunately, the signature pad I've been given only has a COM/ActiveX API, so I've written a short Windows Forms application that will allow the user to capture the signature and upload it to the server. When it is uploaded, I need the MVC4 action to associate the signature image with a specified document entity sent by the Windows Forms request. So, say I have this model:
public class DocumentToSign {
public int DocumentId { get; set; }
public int DocumentTypeId { get; set; }
}
Then I have this action to receive the uploaded image:
[HttpPost]
public ActionResult UploadSignature(DocumentToSign doc, HttpPostedFileBase signature)
{
//do stuff and catch errors to report back to winforms app
return Json(new {Success = true, Error = string.Empty});
}
Then, the code to upload the image:
var doc = new DocumentToSign{ DocumentId = _theId, DocumentTypeId = _theType };
var fileName = SaveTheSignature();
var url = GetTheUrl();
using(var request = new WebClient())
{
request.Headers.Add("enctype", "multipart/form-data");
foreach(var prop in doc.GetType().GetProperties())
{
request.QueryString.Add(prop.Name, Convert.ToString(prop.GetValue(doc, null)));
}
var responseBytes = request.UploadFile(url, fileName);
//deserialize resulting Json, etc.
}
The model binder seems to pick up the DocumentToSign class without any problems, but the HttpPostedFileBase is always null. I know that I need to somehow tell the model binder that the uploaded image is the signature parameter in the action, but I can't figure out how to do it. I tried using UploadValues with a NameValueCollection, but NameValueCollection only allows the value to be a string, so the image (even as a byte[]) can't be part of that.
Is it possible to upload a file as well as a model to the same action from outside of the actual MVC4 application? Should I be using something other than HttpPostedFileBase? Other than the WebClient? I am at a loss.
var responseBytes = request.UploadFile(url, fileName); is not sending your file in the format your controller expect.
HttpPostedFileBase works with multipart/form-data POST request. But WebClient.UploadFile is not sending a multipart request, it sends file content as a body of request with no other information.
You can save the file by calling Request.SaveAs(filename, false);
or you have to change the way you are sending the file. But I don't think WebClient support sending multipart requests.

Categories

Resources