I need to allow image upload in my AngularJS + WebAPI project. to achieve this I am using ng-file-upload according to this sample code: http://monox.mono-software.com/blog/post/Mono/233/Async-upload-using-angular-file-upload-directive-and-net-WebAPI-service/
with a few adjustments to the post code to look like this:
$scope.onFileSelect = function ($files) {
console.log("on file select is running!");
//$files: an array of files selected, each file has name, size, and type.
for (var i = 0; i < $files.length; i++) {
var $file = $files[i];
(function (index) {
Upload.upload({
url: "/api/Uploads/Upload", // webapi url
method: "POST",
file: $file
}).progress(function (evt) {
// get upload percentage
console.log('percent: ' + parseInt(100.0 * evt.loaded / evt.total));
}).success(function (data, status, headers, config) {
// file is uploaded successfully
console.log(data);
}).error(function (data, status, headers, config) {
// file failed to upload
console.log(data);
});
})(i);
}
}
I have quite a few web API controllers already and I added a new one according to the code sample in the link above (that inherits from System.web.http.ApiController instead of the "regular" Microsoft.AspNet.Mvc.Controller class):
[Route("api/[controller]")]
public class UploadsController : ApiController
{
[HttpPost] // This is from System.Web.Http, and not from System.Web.Mvc
public async Task<HttpResponseMessage> Upload()
{
if (!Request.Content.IsMimeMultipartContent())
{
this.Request.CreateResponse(HttpStatusCode.UnsupportedMediaType);
}
var provider = GetMultipartProvider();
var result = await Request.Content.ReadAsMultipartAsync(provider);
// On upload, files are given a generic name like "BodyPart_26d6abe1-3ae1-416a-9429-b35f15e6e5d5"
// so this is how you can get the original file name
var originalFileName = GetDeserializedFileName(result.FileData.First());
// uploadedFileInfo object will give you some additional stuff like file length,
// creation time, directory name, a few filesystem methods etc..
var uploadedFileInfo = new FileInfo(result.FileData.First().LocalFileName);
// Through the request response you can return an object to the Angular controller
// You will be able to access this in the .success callback through its data attribute
// If you want to send something to the .error callback, use the HttpStatusCode.BadRequest instead
var returnData = "ReturnTest";
return this.Request.CreateResponse(HttpStatusCode.OK, new { returnData });
}
The problem is, i keep getting "404 not found" when posting.
I tried:
all stack overflow answers
online answers
removing the content of the "Upload" function, and changing to MVC's Controller base class -> still same result.
changing the name of "Upload" method to "Post" and posting to "/api/uploads" -> same 404.
Please help!
thanks!
EDIT
this is the browser "Network" tab:
I am using HTTPS
this was tested "live" (without localhost) = same result.
EDIT2
my routes:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
these are the only routes that are declared.
EDIT3
I am 100% sure the problem is me using "ApiController" as base class instead of "Controller". i added a new controller and i can access it no problem. now just making it work as i don't have " Request.Content " in "Controller" - any ideas?
I see 2 possibilities to make it work:
pass HttpRequestMessage as a parameter to the method:
public async Task Post([FromBody]HttpRequestMessage request)
but i am getting HTTP 500 as i don't know how to pass from angular's POST.
or:
get Request.Content to resolve under Controller (no idea how).
has someone managed to do this? thanks!
I was able to solve the problem i posted in EDIT3 by using:
if (Request.Form.Files != null && Request.Form.Files.Count > 0)
{
var file = Request.Form.Files[0];
var contentType = file.ContentType;
using (var fileStream = file.OpenReadStream())
{
using (var memoryStream = new MemoryStream())
{
await fileStream.CopyToAsync(memoryStream);
FileStream newFS = new FileStream(Settings.UserImagesDir + "\\name.png", FileMode.Create);
memoryStream.WriteTo(newFS);
newFS.Close();
}
}
}
and all this in a regular web api controller that inherits from Controller.
thanks everyone for your help, although no one solved it you all pushed me in the right direction.
The problem is that your routing is misconfigured for the URL /api/Uploads/Upload. In order to make it match, you have to specify a template for the method Upload, since it is not actually named after an HTTP method.
[HttpPost("upload")]
public async Task<HttpResponseMessage> Upload()
{
// code omitted...
}
Related
I need to send a File (image) from the frontend in Angular along with some more parameters via a POST request to my ASP.NET Core backend to upload the file to a server. The issue is that I either get an HTTP 500 Error depending on the headers I specify or, the most usual, the backend receives the FormData as an empty object.
In Angular, I first convert the Base64 image I receive to a Blob, then a File to construct the FormData (it's what the ngx-compress-image package returns when it compresses an image. Maybe there is a better way to do this too?). Then I assign the headers and send the POST request:
export class RestApiService {
token: string = 'The session token';
userID: string = 'The user ID';
UploadImage(picAsBase64: string) {
let blob = new Blob([picAsBase64], { type: 'image/png' });
let file = new File([blob], Math.random().toString(36).substr(2, 5));
let formData: FormData = new FormData();
formData.append('pfile', file);
const headers = new HttpHeaders({
'Content-Type': 'multipart/form-data',
'Accept': 'application/json'
});
let options = { headers: headers };
let body = {
'paramh': this.token,
'pfile': formData,
'pkuserid': this.userID
}
return this.http.post('api/UploadFiles/UploadFiles/', body, options).pipe(map(data => { return data; }));
}
}
Backend:
[Route("api/[controller]")]
public class UploadFilesController : Controller
{
[HttpPost("UploadFiles")]
public async Task<IActionResult> UploadFiles([FromBody]JObject data)
{
string paramh = data["paramh"].ToString();
IFormFile pfile = data["pfile"].ToObject<IFormFile>();
string pkuserid = data["pkuserid"].ToString();
...
}
}
EDIT
Okay, so I applied Tony's solution, but it didn't work at first. After some tests, I found the solution in declaring the IFormFile variable as a List like Nishant suggested and declaring every argument with [FromForm] like so:
public async Task<IActionResult> UploadFiles([FromForm]string paramh, [FromForm] string pkuserid, [FromForm]List<IFormFile> pfiles)
BUT I still have an issue, because it turns out that my IFormFile has ContentType: application/octet-stream
I don't know if this is the usual and I must convert it from the backend to some image contenttype or something like that, or if it should come from the POST request as an image/png like I declared in the Angular Blob before creating the file.
Variable screenshot
Thank you all again and hope you can still help me with this last issue.
You have to use FormData for your object also like this
let formData: FormData = new FormData();
formData.append('pfile', file);
formData.append('paramh', this.token);
formData.append('pkuserid', this.userID);
return this.http.post('api/UploadFiles/UploadFiles/', formData, options).pipe(map(data => { return data; }));
Also you have to use [FromForm] in your controller
public async Task<IActionResult> UploadFiles([FromForm]JObject data)
Both the urls aren't matching.
I have done it by using IFormFile interface
string emailModel is the Model which will be deserialized
List of IFormFile files for the list of attachments
[HttpPost]
public async Task<IActionResult> SendEmailAsync(string emailModel, List<IFormFile> files)
{
EmailModel emailModel = JsonConvert.DeserializeObject<EmailModelWith>(emailModel);
List<Attachment> attachments = new List<Attachment>();
foreach (var file in files)
{
Attachment attachment = new Attachment(file.OpenReadStream(), file.FileName, file.ContentType);
attachments.Add(attachment);
}
return Ok();
}
I am using jquery file upload to upload large files in chunks to server. I need to send additional data to server along with the file.
I found this article that suggests adding formData as below.
$form = $('#fileupload').fileupload({
maxChunkSize: 3000000,
url: '/FileAPI/Upload',
formData: { example: 'test' }
});
How can I access the formData from HttpContext.Request in asp.net core ? Where is it available?
Thanks for any suggestions.
[HttpPost]
[Route("Upload")]
public ActionResult Upload()
{
var CurrentContext = HttpContext.Request;
}
just add it to the function signature
this should work
[HttpPost]
[Route("Upload")]
public ActionResult Upload(IEnumerable<IFormFile> files, string example)
{
//do something with data
}
How can I access the formData from HttpContext.Request in asp.net core ?
From the article you provide , "formData object, formData: {example: 'test'} will arrive on server-side as POST parameter"
You could also try the following method to get it :
[HttpPost]
[Route("Upload")]
public void Upload()
{
var dict = Request.Form.ToDictionary(x => x.Key, x => x.Value.ToString());
//In that case, you could iterate over your dictionary or you can access values directly:
var CurrentContext = dict["example"];
}
I'm using angular-file-upload as found in Here. It was working for what I needed, but now the need has changed, and I'd like to be able to send additional data about (object) along with the file. There isn't much documentation about it. With what I could see, when I used options directive as attribute and provided the data as object, it's not listed anywhere in FileUploader. Also, my controller gives me an error when I upload. I added a Model class in Post as argument, which is what causing the controller to break.
[HttpPost]
public Task<HttpResponseMessage> PostFile(QAFileAttribute QAFile)
{
this.QAFile = QAFile;
string newLocation = GetCurrentUploadDirectory();
HttpRequestMessage request = this.Request;
if(!request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
string root = UploadLocation; //newLocation;
var provider = new MultipartFormDataStreamProvider(root);
var task = request.Content.ReadAsMultipartAsync(provider)
.ContinueWith<HttpResponseMessage>(o =>
{
if (o.IsFaulted || o.IsCanceled)
{
Request.CreateErrorResponse(HttpStatusCode.InternalServerError, o.Exception);
}
foreach (MultipartFileData file in provider.FileData)
{
if(String.IsNullOrEmpty(file.Headers.ContentDisposition.FileName))
{
return Request.CreateResponse(HttpStatusCode.NotAcceptable, "Filename is not acceptable.");
}
UploadFile(file, QAFile);
}
return Request.CreateResponse(HttpStatusCode.OK);
});
return task;
}// end Post
So, how can I send the multi-part file along with additional data?
This link will sure help you , I have implemented this.
https://uncorkedstudios.com/blog/multipartformdata-file-upload-with-angularjs
app.service('fileUpload', ['$http', function ($http) {
this.uploadFileToUrl = function(file, additional, uploadUrl){
var fd = new FormData();
fd.append('file', file);
$.each(additional, function(obj) {
fd.append(obj.key,obj.value);
});
$http.post(uploadUrl, fd, {
transformRequest: angular.identity,
headers: {'Content-Type': undefined}
})
.success(function(){
})
.error(function(){
});
}
}]);
I figured it out myself. This link helped a lot. Turned out, I didn't have to do a whole lot to provide additional data. I just needed to provide formData as request body to be processed inside the action; the controller action doesn't take any parameters. The problem with my code was that (I want to say due to improper documentation of angular-file-upload) I had misunderstood what formData is. Turned out, it's array of objects. It needed to be like this:
var uploader = new FileUploader({
url: 'api/upload',
formData: [{...}]
});
This sent the data to the controller action as request body. From there, I just had to access it as provider.FormData["somename"];
I have a web Api function that returns a file stream
[HttpPost]
public HttpResponseMessage DownloadDocument([FromBody] parameters)
{
try
{
var stream = FileHelper.GetFilesStream(fileUrl);
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StreamContent(stream) };
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
result.Content.Headers.ContentDisposition.FileName = fileName;
return result;
}
catch (Exception)
{
return Request.CreateErrorResponse(HttpStatusCode.NotFound, "File Not Found");
}
}
How can I call this method in Jquery Ajax to download the file so the save file dialog pops up. I am working with knockout.js, in click event handler of a button, I call this WebAPI method and I get the stream, but I don't know how to save it to the file.
You cannot download a file from an ajax call. You will need to use a get request to download a file.
What you could do is use your ajax post as above but save the file in a db and then return json with the id of the document or a url to download the document. You can then add a hidden iframe which will download the document.
Have a look at this answer which shows how to do it: https://stackoverflow.com/a/16086443/2326610
AFAIK you cannot directly download files via JQuery. One way around this is to declare a hidden iframe in html:
<iframe id='hiddenIframe' src="" style="display:none; visibility:hidden;" ></iframe>
When you click the Download button/link, then in jQuery, you can simply set the iframe source by doing:
$('#downloadButton').click(function(){
$('#hiddenIframe').attr('src', 'api/DownloadDocument');
})
You can get around this by modifying how you are sending data to your webapi.
In your Javascript you can create a hidden form and append the data that you need to that, and then submit it.
Sample code presented in coffeescript, but should be easily read/converted:
downloadDocument: (fileUri, otherProp, successCallback) =>
if $('#hidden-form').length > 0
$('#hidden-form').remove()
$('<form>').attr(
method: 'POST',
id: 'hidden-form',
action: '/your/url/to/method'
).appendTo('body');
$('<input>').attr({
type: 'hidden',
id: 'fileUri',
name: 'fileUri',
value: fileUri
}).appendTo('#hidden-form')
$('<input>').attr({
type: 'hidden',
id: 'otherProp',
name: 'otherProp',
value: otherProp
}).appendTo('#hidden-form')
$('#hidden-form').bind("submit", successCallback)
$('#hidden-form').submit()
I would then also create a DTO object which is taken in as a parameter in your webAPI controller, rather than reading from the request body:
public DownloadDocumentDTO
{
public string fileUri {get;set;}
public object otherProp {get;set;}
}
[HttpPost]
public HttpResponseMessage DownloadDocument(DownloadDocumentDTO _dto)
{
....
}
The code in your controller method should be alright.
It should be noted that if you are trying to pass up more complex data (don't know if you do since its not mentioned), then you will need to add more input to the hidden form, it won't work for passing object up.
I'm uploading files using the ASP.NET Web API. I've done this before the RC but for some reason the file is being saved as "BodyPart_3ded2bfb-40be-4183-b789-9301f93e90af" instead of the file name. The filename variable below returns this bodypart string too instead of the file name. I can't seem to figure out where I'm going wrong. Any help is appreciated.
Client code:
function upload() {
$("#divResult").html("Uploading...");
var formData = new FormData($('form')[0]);
$.ajax({
url: 'api/files/uploadfile?folder=' + $('#ddlFolders').val(),
type: 'POST',
success: function (data) {
$("#divResult").html(data);
},
data: formData,
cache: false,
contentType: false,
processData: false
});
};
Controller:
public Task<HttpResponseMessage> UploadFile([FromUri]string folder)
{
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.UnsupportedMediaType));
}
// Save file
MultipartFormDataStreamProvider provider = new MultipartFormDataStreamProvider(HttpContext.Current.Server.MapPath("~/Files"));
Task<IEnumerable<HttpContent>> task = Request.Content.ReadAsMultipartAsync(provider);
return task.ContinueWith<HttpResponseMessage>(contents =>
{
string filename = provider.BodyPartFileNames.First().Value;
return new HttpResponseMessage()
{
Content = new StringContent(string.Format("File saved in {0}.", folder))
};
}, TaskScheduler.FromCurrentSynchronizationContext());
The files are looking like:
That was a concious change we made -- it was considered a security risk to take the file name provided in the Content-Disposition header field and so instead we now compute a file name which is what you are seeing.
If you want to control the server local file name yourself then you can derive from MultipartFormDataStreamProvider and override GetLocalFileName to provide whatever name you want. Note though that there may be security considerations doing so.
Hope this helps,
Henrik
I updated the code for the tutorial to make it work with ASP.NET Web API RC. Indeed, as Henrik mentioned Content-Disposition is no longer used a file name. See the source files at the bottom of the post - http://www.strathweb.com/2012/04/html5-drag-and-drop-asynchronous-multi-file-upload-with-asp-net-webapi/
Please note, that there are further changes to MultipartFormDataStreamProvider that didn't make the cut to the RC, so it's now even more flexible. Henrik blogged about those here - http://blogs.msdn.com/b/henrikn/archive/2012/04/27/asp-net-web-api-updates-april-27.aspx.
EDIT: I have blogged about new and improved way of uploading files in Web API RTM, so that should hopefully help gets things organized - http://www.strathweb.com/2012/08/a-guide-to-asynchronous-file-uploads-in-asp-net-web-api-rtm/
Here, this work for me
In API Controller
// We implement MultipartFormDataStreamProvider to override the filename of File which
// will be stored on server, or else the default name will be of the format like Body-
// Part_{GUID}. In the following implementation we simply get the FileName from
// ContentDisposition Header of the Request Body.
public class CustomMultipartFormDataStreamProvider : MultipartFormDataStreamProvider
{
public CustomMultipartFormDataStreamProvider(string path) : base(path) { }
public override string GetLocalFileName(HttpContentHeaders headers)
{
return headers.ContentDisposition.FileName.Replace("\"", string.Empty);
}
}
Then
string root = HttpContext.Current.Server.MapPath("~/App_Data");
CustomMultipartFormDataStreamProvider provider = new CustomMultipartFormDataStreamProvider(root);
Thanks,