Getting form data from HttpListenerRequest - c#

I have a HttpListenerRequest which was initiated from a html <form> that was posted. I need to know how to get the posted form values + the uploaded files. Does anyone know of an example to save me the time doing it for myself? I've had a google around but not found anything of use.

The main thing to understand is that HttpListener is a low level tool to work with http requests. All post data is in HttpListenerRequest.InputStream stream. Suppose we have a form like that:
<form method=\"post\" enctype=\"multipart/form-data\"><input id=\"fileUp\" name=\"fileUpload\" type=\"file\" /><input type=\"submit\" /></form>
Now we want to see the post data. Lets implement a method to do this:
public static string GetRequestPostData(HttpListenerRequest request)
{
if (!request.HasEntityBody)
{
return null;
}
using (System.IO.Stream body = request.InputStream) // here we have data
{
using (var reader = new System.IO.StreamReader(body, request.ContentEncoding))
{
return reader.ReadToEnd();
}
}
}
upload some file and see result:
Content-Disposition: form-data; name="somename"; filename="D:\Test.bmp"
Content-Type: image/bmp
...here is the raw file data...
Next suppose we have simple form without uploading files:
<form method=\"post\">First name: <input type=\"text\" name=\"firstname\" /><br />Last name: <input type=\"text\" name=\"lastname\" /><input type=\"submit\" value=\"Submit\" /></form>
Let's see the output:
firstname=MyName&lastname=MyLastName
Combined form result:
Content-Disposition: form-data; name="firstname"
My Name
Content-Disposition: form-data; name="somename"; filename="D:\test.xls"
Content-Type: application/octet-stream
...raw file data...
As you can see in case of simple form you can just read InputStream to string and parse post values. If there is a more complex form - you need to perform more complex parsing but it's still can be done. Hope this examples will save your time. Note, that is not always the case to read all stream as a string.

Odd that an accepted answer with full source code + a link to download
a working demo would result in a negative score and votes to be
deleted. If I were the kind of person who cared about my score I'd
have deleted this answer, and then nobody would have benefited.
Psychology, eh :)
I found a few examples of web servers for MonoTouch but none of them parsed the data sent in a POST request. I looked around the web and was unable to find any examples of how to achieve this. So now that I’ve written it myself I’ve decided to share my own implementation. This includes not only the code for processing the form post data but also for registering request handlers etc.
Here is an example of how you would use the web server
public BookUploadViewController()
: base("BookUploadViewController", null)
{
RequestHandler = new DefaultRequestHandler();
var defaultActionHandlerFactory = new DefaultActionHandlerFactory();
RegisterActionHandlers(defaultActionHandlerFactory);
RequestHandler.AddActionHandlerFactory(defaultActionHandlerFactory);
WebServer = new EmbeddedWebServer(RequestHandler);
}
void RegisterActionHandlers(DefaultActionHandlerFactory factory)
{
factory.RegisterHandler(
request => request.RawUrl == "/",
request => new IndexActionHandler(request)
);
factory.RegisterHandler(
request =>
string.Compare(request.RawUrl, "/Upload", true) == 0 &&
string.Compare(request.HttpMethod, "POST", true) == 0,
request => new UploadActionHandler(request)
);
}
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
StatusLabel.Text = string.Format("Server listening on\r\nhttp://{0}:8080", GetIPAddress ());
WebServer.Start(8080);
}
public override void ViewDidDisappear (bool animated)
{
base.ViewDidDisappear(animated);
WebServer.Stop();
}
And here are two app specific examples of request handlers
class IndexActionHandler : DefaultActionHandler
{
public IndexActionHandler(HttpListenerRequest request)
: base(request)
{
}
public override ActionResult Execute()
{
var result = new HtmlResult();
result.AppendLine("<html>");
result.AppendLine("<body>");
result.AppendLine("<h1>Upload an image</h1>");
result.AppendLine("<form action='/Upload' enctype='multipart/form-data' method='post'>");
result.AppendLine ("<input name='Image' type='file'/><br/>");
result.AppendLine("<input name='Upload' type='submit' text='Upload'/>");
result.AppendLine("</form>");
result.AppendLine("</body>");
result.AppendLine("</html>");
return result;
}
}
class UploadActionHandler : DefaultActionHandler
{
public UploadActionHandler(HttpListenerRequest request)
: base(request)
{
}
public override ActionResult Execute()
{
string errorMessage = null;
var file = FormData.GetFile("Image");
if (file == null
|| file.FileData == null
|| file.FileData.Length == 0
|| string.IsNullOrEmpty(file.FileName))
errorMessage = "No image uploaded";
if (errorMessage == null)
ProcessFile(file);
var result = new HtmlResult();
result.AppendLine("<html>");
result.AppendLine("<body>");
if (errorMessage == null)
result.AppendLine("<h1>File uploaded successfully</h1>");
else
{
result.AppendLine("<h1>Error</h1>");
result.AppendLine("<h2>" + errorMessage + "</h2>");
}
result.AppendLine("</body>");
result.AppendLine("</html>");
return result;
}
void ProcessFile(MultiPartStreamFileValue postedFile)
{
string fileName = "Where to save the file";
using (var fileStream =
new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None))
{
fileStream.Write(postedFile.FileData, 0, postedFile.FileData.Length);
}
}
}
You can download the source code here https://sites.google.com/site/mrpmorris/EmbeddedWebServerMT.zip

It's an old thread but I think the demand still applies. In newer versions of .Net (tested on net6.0) it is possible to avoid manual parsing of application/x-www-form-urlencoded content using the standard function System.Web.HttpUtility.ParseQueryString(string content).
One still needs to manually read the content as text from the request input stream, as shown in the accepted answer above.

Related

How to create downloadable file link for the file to download in ASP.net core

Previously I was sending file as Byte array from ASP.net core 2.0 and in Angular 4 application I am calling below function to download the file
function (response) { // Here response is byte array
var url= window.URL.createObjectURL(res);
var link = document.createElement("a");
link.setAttribute("href", url);
link.setAttribute("download", this.zipLocation + ".zip");
link.style.display = "none";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
But now I want to send the file path from the server like below
https://websiteaddress/file/path/to/download.ext
So in Angular 5, I can directly attach link to href attribute of anchor tag and will make automatic click on that. So I don't need to Convert byte array to url
Here the issue is I don't know how to create that downloadable file path using ASP.net core and send it to frontend
And also I want to know, which approach is better, whether sending Byte array or Sending the direct link? Is there any performance issue with any of the two?
If you are using api response as file data
add responseType: 'arraybuffer' in request header.
Try something like this:
HTML:
<a (click)="downLoad()">Click To Download</a>
TS:
downLoad(){
this.fileService.getFileFromServer(fileId.toString()).subscribe(respData => {
this.downLoadFile(respData, this.type);
}, error => {
});
}
/**
* Method is use to download file.
* #param data - Array Buffer data
* #param type - type of the document.
*/
downLoadFile(data: any, type: string) {
var blob = new Blob([data], { type: type.toString() });
var url = window.URL.createObjectURL(blob);
var pwa = window.open(url);
if (!pwa || pwa.closed || typeof pwa.closed == 'undefined') {
console.log('Please disable your Pop-up blocker and try again');
}
}
file-service.ts:
getFileFromServer(id){
return this.http.get(url, {responseType: 'arraybuffer',headers:headers});
}
your question make confuse about angular frontend and backend
frontend you can use mvc
<a asp-controller="Controller"
asp-action="Download"
asp-route-id="#Model.FileName">Download #Model.FileName</a>
or using angular
Download
<a [href]="ControllerRoute+'/Download?name='+fileName" download>Download {{fileName}}</a>
Ok maybe your problem is your action (in controller) doesnt server a file
you need return a HttpResponse with a MediaType, this is just a example, dont forget best practices on your code
[HttpGet]
public HttpResponseMessage GetDownloadableFIle(string name)
{
try
{
var result = new HttpResponseMessage(HttpStatusCode.OK);
var filePath = $"{MyRootPath}/{name}";
var bytes = File.ReadAllBytes(filePath );
result.Content = new ByteArrayContent(bytes);
var mediaType = "application/octet-stream";
result.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(mediaType);
return result;
}
catch (Exception ex)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.InternalServerError, ex.ToString()));
}
}

Request.Content.IsMimeMultipartContent() fails

I'm trying to upload a file using the Html2 input type="file" and an angular2 http.post request. When the request reaches the web api, it fails in the
Request.Content.IsMimeMultipartContent()
It doesn't fail when submitting the request using Postman (when I don't include Content-Type in the header because postman takes care of it).
See my code:
Html:
<input type="file" (change)="fileChange($event)" placeholder="Upload file" accept=".pdf,.doc,.docx,.dwg,.jpeg,.jpg">
Service function:
uploadFile(event) {
let fileUploadUrl = this.webApiFileUploadURL;
let fileList: FileList = event.target.files;
if(fileList.length > 0) {
let file: File = fileList[0];
let formData:FormData = new FormData();
formData.append('uploadFile', file, file.name);
let headers = new Headers();
headers.append('Content-Type', 'multipart/form-data');
headers.append('Accept', 'application/json');
let options = new RequestOptions({ headers: headers });
this._http.post(`${this.webApiFileUploadURL}`, formData, options)
.map(res => res.json())
.catch(error => Observable.throw(error))
.subscribe(
data => console.log('success'),
error => console.log(error)
)
}
And the WebApi post request (fails at
if (!Request.Content.IsMimeMultipartContent()) ):
public async Task<HttpResponseMessage> PostFormData()
{
// Check if the request contains multipart/form-data.
if (!Request.Content.IsMimeMultipartContent()) // Fails here
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
try
{
// Read the form data.
await Request.Content.ReadAsMultipartAsync(provider);
// This illustrates how to get the file names.
foreach (MultipartFileData file in provider.FileData)
{
Trace.WriteLine(file.Headers.ContentDisposition.FileName);
Trace.WriteLine("Server file path: " + file.LocalFileName);
}
return Request.CreateResponse(HttpStatusCode.OK);
}
catch (System.Exception e)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e);
}
}
After a thorough research - I've succeeded:
No need to set the content-type header property when posting.
I've removed it from my angular2 http.post request and the
Request.Content.IsMimeMultipartContent() in the web-api post method passed (same as in postman)
If anyone else runs into this "The request entity's media type 'multipart/form-data' is not supported for this resource."
You may need to add this in you webapiconfig
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data"));
Original Credit
After so much reading, my guess is that you need to save your uploaded files asychronously (i just refer to it as JQUERY_D_UPLOAD).
I've created this small ASP.NET C# async task below to get you started.
NOTE: should return a string eg return "This was created by onyangofred#gmail.com";
For more, find me in my turing.com/github/facebook/gmail accounts: (onyangofred)
public async Task<string> SaveFile()
{
for (int i = 0; i < Request.Files.Count; i++)
{
HttpPostedFileBase file = Request.Files[i];
using (var stream = new FileStream(Path.Combine("~/uploads", Guid.NewGuid().ToString() + Path.GetExtension(file.FileName)), FileMode.Create, FileAccess.Write, FileShare.Write, 4096, useAsync: true))
{
await file.InputStream.CopyToAsync(stream);
}
}
}

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

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

Get file with querystring from formData on c# server side using xmlhttprequest

I have been stuck on this for a couple hours now and not even google can help anymore. I am trying to send a file from the client to the backend using xmlhttprequest. I cannot get the filename, type, or content on the C# side. I would appreciate help on doing this. A lot of code I came across had methods that I can only guess are not supported in ASP.Net 5 and MVC 6 (such as HttpContext.Current and HttpPostedFile)
Here is my client side JavaScript request. This sends the query strings which bind to my model no problem so that is easily accessible, but getting the file is what I am having trouble with.
var form = new FormData();
form.append("file", file);
var queryParams = "id=" + (id == null ? -1 : id);
queryParams += "&name=" + name;
queryParams += "&value=" + val;
xhrAttach(REST_DATA + "/attach?" + queryParams, form, function (item) {
console.log('attached: ', item);
alert(item.responseText);
row.setAttribute('data-id', item.id);
removeProgressIndicator(row);
setRowContent(item, row);
}, function (err) {
console.log(err);
//stop showing loading message
stopLoadingMessage();
document.getElementById('errorDiv').innerHTML = err;
});
function xhrAttach(url, data, callback, errback)
{
var xhr = new createXHR();
xhr.open("POST", url, true);
//xhr.setRequestHeader("Content-type", "multipart/form-data");
xhr.onreadystatechange = function(){
if(xhr.readyState == 4){
if(xhr.status == 200){
callback(parseJson(xhr.responseText));
}else{
errback("Error: "+xhr.responseText);
}
}
};
xhr.timeout = 1000000;
xhr.ontimeout = errback;
xhr.send(data);
}
Here is my Controller dealing with the request. attachment is a model and the query string binds to it no problem. I could not find out how to add a File parameter to the model, or if that would even matter. Things I have tried are under this code.
// POST: /api/db/attach
[Route("/api/[controller]/attach")]
[HttpPost]
public async Task<dynamic> attach(Attachment attachment)
{
//get the file somehow
}
i have tried many things, but cannot remember exactly what, here is one thing I did try though, which did not work.
var file = Request.Form["file"];
here is the attachment model in case it helps
namespace MyModel.Models
{
public class Attachment
{
public long id { get; set; }
public string name { get; set; }
public string value { get; set; }
}
}
Don't use query parameters or FormData if you're going to use a model on the MVC side. Just don't. And to me, it's better to just get the file into a base64 string first, than to try sending the File object, itself. I've posted about how to do that, here: Convert input=file to byte array
Then, declare and format a JSON object:
var dataObj = {
file = fileByteArray[0],
id = (id == null ? -1 : id),
name = name,
value = val
};
That fileByteArray[0] is referencing the object from my link. My answer there assumes you were just going to keep loading file base64 strings into that global array object. You can either keep it as an array, like I had, and loop through them one by one, replacing that [0] with [i], for example, as the indexer in a for loop, or just use a var fileByteArray = "" with that other code, make it so you don't push additional files but always just overwrite that variable, & just use that.
And a word of caution on that last parameter - don't use val if you use jQuery - it's a keyword. I only have it above because it's what you were passing to the URL parameter values.
Get rid of the queryParams in this line:
xhrAttach(REST_DATA + "/attach?" + queryParams, form, function (item) {
Change it to:
xhrAttach(REST_DATA + "/attach", form, function (item) {
Set the Content-Type, right where it's commented out, to:
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
Change what you are sending - it's no longer going to be FormData, it's the JSON object, and it needs to be stringified:
xhr.send(JSON.stringify(dataObj));
Fix your model to now include the file base64 string:
public class Attachment
{
public string file { get; set; }
public long id { get; set; }
public string name { get; set; }
public string value { get; set; }
}
Fix your POST method. 2 issues:
You can't use [HttpPost] if your class is inheriting ApiController, which you probably should be for this. It must be [System.Web.Http.HttpPost], and yes, it has to be completely spelled out, or it will assume it's [System.Web.Mvc.HttpPost] and not assign the route - you'd get a 404 - Not Found error when you try to do your POST. If you're inheriting from Controller, disregard this.
You will need a [FromBody] tag on your model if you are inheriting from ApiController:
public async Task<dynamic> attach([FromBody]Attachment attachment) { ... }
Then you get the file like this:
string base64FileString = attachment.file;
If you want to store it in a byte[] in the database, you can convert it:
byte[] bytes = System.Convert.FromBase64String(base64FileString);
And btw, I think your response handling is wrong. I would not do this:
xhr.onreadystatechange = function(){
if(xhr.readyState == 4){
if(xhr.status == 200){
callback(parseJson(xhr.responseText));
}else{
errback("Error: "+xhr.responseText);
}
}
};
This is how I would do it:
xhr.onreadystatechange = function(response){
if(xhr.readyState == 4 && xhr.status == 200){
callback(parseJson(response.target.responseText));
} else {
alert("Error: " + response.target.responseText);
}
};
Assuming that the response.target.responseText is getting the error sent back from the server-side in a way you can display. If not, sending it to a function that could parse it out would be the right choice. I don't think that xhr.responseText was correct.
I would suggest trying the following:
public async Task<dynamic> attach([FromURI]Attachment attachment, [FromBody] FormDataCollection formData)
And then the FormDataCollection should have the form data for retrieval.
Add a public get/set property called File to your Attachment model and the uploaded file should be bound to this property.
A model similar to yours:
https://github.com/aspnet/Mvc/blob/9f9dcbe6ec2e34d8a0dfae283cb5e40d8b94fdb7/test/WebSites/ModelBindingWebSite/Models/Book.cs#L8
public class Book
{
public string Name { get; set; }
public IFormFile File { get; set; }
}
Following controller has examples of different ways of model binding an uploaded file.
https://github.com/aspnet/Mvc/blob/9f9dcbe6ec2e34d8a0dfae283cb5e40d8b94fdb7/test/WebSites/ModelBindingWebSite/Controllers/FileUploadController.cs#L81
public KeyValuePair<string, FileDetails> UploadModelWithFile(Book book)
{
var file = book.File;
var reader = new StreamReader(file.OpenReadStream());
var fileContent = reader.ReadToEnd();
var parsedContentDisposition = ContentDispositionHeaderValue.Parse(file.ContentDisposition);
var fileDetails = new FileDetails
{
Filename = parsedContentDisposition.FileName,
Content = fileContent
};
return new KeyValuePair<string, FileDetails>(book.Name, fileDetails);
}
if this does not work, then I suspect your request is not in correct format.

Categories

Resources