Multiple form-data file upload parameters - c#

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);
}

Related

Post large files to REST Endpoint c# .net core

I need to post large files in chunks to an external API. The files type is an MP4 that I have downloaded to my local system and they can be up to 4 gig in size. I need to chunk this data and send it out. Everything I looked at deals with the posting from a Web front end (Angular, JS, etc) and handling the chunked data on the controller. I need to take the file that I have saved local and chunk it up and send it off to an existing API that is expecting chunked data.
Thanks
I think these 2 links can help you to achieve what you need, normally the IFormFile has restrictions for big files, in this case, you need to stream it.
This is from MVC 2 and will help you to understand the HttpPostedFileBase approach
Same approach but wrapping it into a class
Asp.net core 2.2 has the correct example on the documentation in case you want to upload bigger files : See this section
The idea behind is to stream the content, for that, you need to disable the bindings that Asp.net core has and start streaming the content that was posted/uploaded.
After you receive that information, then you use the FormValueProvider to rebind all the key/value you received from the client.
Because you are using multipart content type, you need to be aware that all the content will not come in the same order, maybe you receive the file, later other parameters or vice-versa.
[HttpPost(Name = "CreateDocumentForApplication")]
[DisableFormValueModelBinding]
public async Task<IActionResult> CreateDocumentForApplication(Guid tenantId, Guid applicationId, DocumentForCreationDto docForCreationDto, [FromHeader(Name = "Accept")] string mediaType)
{
//use here the code of the asp.net core documentation on the Upload method to read the file and save it, also get your model correctly after the binding
}
you can notice that I am passing more parameters as part of the post like DocumentForCreationDto, but the approach is the same(disable the binding)
public class DocumentForCreationDto : IDto
{
//public string CreatedBy { get; set; }
public string DocumentName { get; set; }
public string MimeType { get; set; }
public ICollection<DocumentTagInfoForCreationDto> Tags { get; set; }
}
If you want to use the postman, see how I am passing the paremeters:
If you want to upload it via code here is the pseudocode:
void Upload()
{
var content = new MultipartFormDataContent();
// Add the file
var fileContent = new ByteArrayContent(file);
fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data")
{
FileName = fileName,
FileNameStar = "file"
};
content.Add(fileContent);
//this is the way to add more content to the post
content.Add(new StringContent(documentUploadDto.DocumentName), "DocumentName");
var url = "myapi.com/api/documents";
HttpResponseMessage response = null;
try
{
response = await _httpClient.PostAsync(url, content);
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
Hope this helps

Passing file and object to body in Post

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
}

ASP.NET Web API object parameter not being filled on post request

I am trying to set up a small ASP.NET Web API projects so I can post data to the database from a small React.JS project. I tried alot of sollutions but the results made no sense and I have no idea how to fix it anymore.
I have this very simple model:
public class Hour
{
public int WeekID { get; set; }
}
And this is my controller
[HttpPost]
public IHttpActionResult AddHour(Hour hour)
{
return Ok();
}
This is the method that I use to POST my data
export const SaveWeek = weekData=> {
const headers = new Headers();
headers.append("Content-Type", "application/json");
const Week= {
method: "POST",
headers,
mode: "cors",
body: weekData
};
console.log("Hours:");
// Returns {"WeekID": 1}
console.log(Hours.body);
return axios.post("http://localhost:52350/api/REST/AddHour", {
Week
});
};
The way I call this SaveWeek method in React is:
// The JSON parameter is for testing hard coded to: {"WeekID": 1}
handleSave = async json => {
const data = await SaveWeek(json);
console.log(data);
this.closeModal();
};
I know that the axios POST request works, the way I tested that is by changing the method to not use any parameters and looking at the result that where received:
[HttpPost]
public IHttpActionResult AddHour(Hour hour)
{
// This returns a string in which the data that I sent
// can be found.
string body = Request.Content.ReadAsStringAsync().Result;
return Ok();
}
The weird thing is that the body will be filled with data when the method does not contain any parameters, but when I provide the method with the Hour object parameter the body will be an empty string (""). And also the Hour object parameter wont be filled with the values that I provide it.
What am I doing wrong here?
According to https://github.com/axios/axios#axiosposturl-data-config axios.post has following signature
axios.post(url[, data[, config]])
So you just need to change your request to
export const SaveWeek = weekData => {
//headers should be simple object, not Headers
const headers = {
"Content-Type": "application/json"
};
//removed body, because we pass data as second parameter
//removed method, because 'axios.post' implies using "post" method
const Config = {
headers,
mode: "cors"
};
const url = "http://localhost:52350/api/REST/AddHour";
return axios.post(url, weekData, Config);
}
An incoming request to the ASP.Net Web API pipeline is read as a forward-only stream for super speed. Once it has been read it cannot be read again.
[HttpPost]
public IHttpActionResult AddHour(Hour hour)
{
// With model binding
// use hour.WeekID
}
In this first example model binding is already done and once it has been read it cannot be read again. Hence, Request.Content will be empty after that.
[HttpPost]
public IHttpActionResult AddHour()
{
// Without model binding
// use Request.Content
}
In second example it does not use model binding therefore still has the Request.Content property populated.
Use one or the other, not both, do not mix with MVC model binding which works differently.
A better explanation is available in this blog post
http://www.hackered.co.uk/articles/asp-net-web-api-why-is-the-request-Content-empty-when-the-model-is-populated

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